mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-05 23:19:42 +00:00
feat: weird impl
This commit is contained in:
parent
7b0c49f565
commit
fca0551032
@ -45,6 +45,7 @@ import 'package:spotube/services/kv_store/encrypted_kv_store.dart';
|
||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
import 'package:spotube/services/wm_tools/wm_tools.dart';
|
||||
import 'package:spotube/src/plugin_api/webview/webview_binding.dart';
|
||||
import 'package:spotube/src/rust/api/plugin/models/core.dart';
|
||||
import 'package:spotube/src/rust/api/plugin/plugin.dart';
|
||||
import 'package:spotube/src/rust/frb_generated.dart';
|
||||
@ -58,28 +59,6 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:yt_dlp_dart/yt_dlp_dart.dart';
|
||||
import 'package:flutter_new_pipe_extractor/flutter_new_pipe_extractor.dart';
|
||||
|
||||
const pluginJS = """
|
||||
function timeout(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
class CoreEndpoint {
|
||||
async checkUpdate() {
|
||||
console.log('Core checkUpdate');
|
||||
await timeout(5000);
|
||||
console.log('Core checkUpdate done. No updates!');
|
||||
}
|
||||
get support() {
|
||||
return 'Metadata';
|
||||
}
|
||||
}
|
||||
|
||||
class TestingPlugin {
|
||||
constructor() {
|
||||
this.core = new CoreEndpoint();
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
Future<void> main(List<String> rawArgs) async {
|
||||
if (rawArgs.contains("web_view_title_bar")) {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@ -120,25 +99,44 @@ Future<void> main(List<String> rawArgs) async {
|
||||
await KVStoreService.initialize();
|
||||
|
||||
await RustLib.init();
|
||||
WebViewBinding.register();
|
||||
|
||||
final plugin = SpotubePlugin();
|
||||
const config = PluginConfiguration(
|
||||
entryPoint: "TestingPlugin",
|
||||
abilities: [PluginAbility.metadata],
|
||||
apis: [],
|
||||
author: "KRTirtho",
|
||||
description: "Testing Plugin",
|
||||
name: "Testing Plugin",
|
||||
const pluginConfiguration = PluginConfiguration(
|
||||
name: "Spotube Plugin",
|
||||
description: "Spotube Plugin",
|
||||
version: "1.0.0",
|
||||
author: "Spotube",
|
||||
entryPoint: "Plugin",
|
||||
pluginApiVersion: "2.0.0",
|
||||
repository: null,
|
||||
version: "0.1.0",
|
||||
apis: [PluginApi.localstorage, PluginApi.webview],
|
||||
abilities: [PluginAbility.metadata],
|
||||
);
|
||||
final sender = plugin.createContext(
|
||||
pluginScript: pluginJS,
|
||||
pluginConfig: config,
|
||||
final pluginContext = plugin.createContext(
|
||||
pluginScript: """
|
||||
class AuthEndpoint {
|
||||
}
|
||||
class CoreEndpoint {
|
||||
async checkUpdate() {
|
||||
const webview = await Webview.create("https://spotube.krtirtho.dev");
|
||||
webview.events.on("url_change", (url) => {
|
||||
console.log("url_change: ", url);
|
||||
})
|
||||
await webview.open();
|
||||
}
|
||||
}
|
||||
class Plugin {
|
||||
constructor() {
|
||||
this.auth = new AuthEndpoint();
|
||||
this.core = new CoreEndpoint();
|
||||
}
|
||||
}
|
||||
""",
|
||||
pluginConfig: pluginConfiguration,
|
||||
);
|
||||
|
||||
await plugin.core.checkUpdate(mpscTx: sender, pluginConfig: config);
|
||||
await plugin.core
|
||||
.checkUpdate(mpscTx: pluginContext, pluginConfig: pluginConfiguration);
|
||||
|
||||
if (kIsDesktop) {
|
||||
await windowManager.setPreventClose(true);
|
||||
|
||||
0
lib/src/plugin_api/localstorage/localstorage.dart
Normal file
0
lib/src/plugin_api/localstorage/localstorage.dart
Normal file
119
lib/src/plugin_api/webview/webview.dart
Normal file
119
lib/src/plugin_api/webview/webview.dart
Normal file
@ -0,0 +1,119 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart'
|
||||
as webview_window;
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:random_user_agents/random_user_agents.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' hide join;
|
||||
import 'package:spotube/collections/routes.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
import 'package:spotube/src/plugin_api/webview/webview_page.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class Webview {
|
||||
final String uri;
|
||||
final String uid;
|
||||
Webview({
|
||||
required this.uri,
|
||||
}) : _onUrlRequestStreamController = StreamController<String>.broadcast(),
|
||||
uid = const Uuid().v4();
|
||||
|
||||
StreamController<String>? _onUrlRequestStreamController;
|
||||
Stream<String> get onUrlRequestStream =>
|
||||
_onUrlRequestStreamController!.stream;
|
||||
|
||||
webview_window.Webview? _webview;
|
||||
|
||||
BuildContext? _pageContext;
|
||||
Future<void> open() async {
|
||||
if (Platform.isLinux) {
|
||||
final applicationSupportDir = await getApplicationSupportDirectory();
|
||||
final userDataFolder = Directory(
|
||||
join(applicationSupportDir.path, "webview_window_Webview2"),
|
||||
);
|
||||
|
||||
if (!await userDataFolder.exists()) {
|
||||
await userDataFolder.create();
|
||||
}
|
||||
|
||||
_webview = await WebviewWindow.create(
|
||||
configuration: CreateConfiguration(
|
||||
title: "Spotube Login",
|
||||
windowHeight: 720,
|
||||
windowWidth: 1280,
|
||||
userDataFolderWindows: userDataFolder.path,
|
||||
),
|
||||
)
|
||||
..setApplicationUserAgent(RandomUserAgents.random());
|
||||
_webview!.setOnUrlRequestCallback((url) {
|
||||
_onUrlRequestStreamController?.add(url);
|
||||
return true;
|
||||
});
|
||||
_webview!.launch(uri);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
final route = WebviewPage(
|
||||
uri: uri,
|
||||
onLoad: (url) {
|
||||
_onUrlRequestStreamController?.add(url.toString());
|
||||
},
|
||||
);
|
||||
|
||||
await rootNavigatorKey.currentContext?.router.pushWidget(
|
||||
Builder(builder: (context) {
|
||||
_pageContext = context;
|
||||
return Scaffold(
|
||||
headers: const [
|
||||
TitleBar(
|
||||
automaticallyImplyLeading: true,
|
||||
)
|
||||
],
|
||||
child: route,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
_onUrlRequestStreamController?.close();
|
||||
_onUrlRequestStreamController = null;
|
||||
if (Platform.isLinux) {
|
||||
_webview?.close();
|
||||
_webview = null;
|
||||
return;
|
||||
}
|
||||
await _pageContext?.maybePop();
|
||||
}
|
||||
|
||||
Future<List<Cookie>> getCookies(String url) async {
|
||||
if (Platform.isLinux) {
|
||||
final cookies = await _webview?.getAllCookies() ?? [];
|
||||
|
||||
return cookies.map((cookie) {
|
||||
return Cookie(
|
||||
name: cookie.name,
|
||||
value: cookie.value,
|
||||
domain: cookie.domain,
|
||||
expiresDate: cookie.expires?.millisecondsSinceEpoch,
|
||||
isHttpOnly: cookie.httpOnly,
|
||||
isSecure: cookie.secure,
|
||||
isSessionOnly: cookie.sessionOnly,
|
||||
path: cookie.path,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return await CookieManager.instance(
|
||||
// Created in [WebviewPage]. Custom WebViewEnvironment for Windows otherwise it installs
|
||||
// in installation directory so permission exception occurs.
|
||||
webViewEnvironment: await webViewEnvironment,
|
||||
).getCookies(url: WebUri(url));
|
||||
}
|
||||
}
|
||||
40
lib/src/plugin_api/webview/webview_binding.dart
Normal file
40
lib/src/plugin_api/webview/webview_binding.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
import 'package:spotube/src/plugin_api/webview/webview.dart';
|
||||
import 'package:spotube/src/rust/api/host_api/webview.dart';
|
||||
|
||||
class WebViewBinding {
|
||||
static void register() async {
|
||||
final subscriptions = <String, StreamSubscription>{};
|
||||
|
||||
await initializeWebviewCallbacks(
|
||||
createWebview: (uri, sender) async {
|
||||
final webview = Webview(uri: uri);
|
||||
|
||||
subscriptions[webview.uid] =
|
||||
webview.onUrlRequestStream.listen((event) async {
|
||||
try {
|
||||
await sendWebviewEvents(tx: sender, event: event);
|
||||
} catch (e, stack) {
|
||||
AppLogger.reportError(e, stack);
|
||||
}
|
||||
});
|
||||
|
||||
return webview;
|
||||
},
|
||||
openWebview: (webview) async {
|
||||
await (webview as Webview).open();
|
||||
},
|
||||
closeWebview: (webview) async {
|
||||
subscriptions.remove((webview as Webview).uid);
|
||||
await webview.close();
|
||||
},
|
||||
getCookies: (webview, url) async {
|
||||
final cookies = await (webview as Webview).getCookies(url);
|
||||
return jsonEncode(cookies);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
62
lib/src/plugin_api/webview/webview_page.dart
Normal file
62
lib/src/plugin_api/webview/webview_page.dart
Normal file
@ -0,0 +1,62 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:fk_user_agent/fk_user_agent.dart';
|
||||
|
||||
Future<String?> getUserAgent() async {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
await FkUserAgent.init();
|
||||
return FkUserAgent.userAgent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final webViewEnvironment = Platform.isWindows
|
||||
? getApplicationSupportDirectory().then((directory) async {
|
||||
return await WebViewEnvironment.create(
|
||||
settings: WebViewEnvironmentSettings(
|
||||
userDataFolder: join(directory.path, 'inappwebview_data'),
|
||||
),
|
||||
);
|
||||
})
|
||||
: Future.value(null);
|
||||
|
||||
class WebviewPage extends StatelessWidget {
|
||||
final String uri;
|
||||
final void Function(String url)? onLoad;
|
||||
const WebviewPage({super.key, required this.uri, this.onLoad});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: Future.wait([webViewEnvironment, getUserAgent()]),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return InAppWebView(
|
||||
initialUrlRequest: URLRequest(url: WebUri(uri)),
|
||||
webViewEnvironment: snapshot.data?[0] as WebViewEnvironment?,
|
||||
initialSettings: InAppWebViewSettings(
|
||||
userAgent: snapshot.data?[1] as String?,
|
||||
),
|
||||
onLoadStop: (controller, url) {
|
||||
try {
|
||||
if (onLoad != null && url != null) {
|
||||
onLoad!(url.toString());
|
||||
}
|
||||
} catch (e, stack) {
|
||||
debugPrint("[Webview][onLoad] Error: $e");
|
||||
debugPrintStack(stackTrace: stack);
|
||||
rethrow;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/src/rust/api/host_api/webview.dart
Normal file
31
lib/src/rust/api/host_api/webview.dart
Normal file
@ -0,0 +1,31 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.11.1.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import '../../frb_generated.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `DART_CLOSE_WEBVIEW`, `DART_CREATE_WEBVIEW`, `DART_GET_COOKIES`, `DART_OPEN_WEBVIEW`, `HostWebview`, `Webview`
|
||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `deref`, `deref`, `deref`, `deref`, `initialize`, `initialize`, `initialize`, `initialize`, `trace`
|
||||
// These functions are ignored (category: IgnoreBecauseExplicitAttribute): `close`, `close`, `create`, `create`, `get_cookies`, `get_cookies`, `open`, `open`, `poll_url_change_event`
|
||||
|
||||
Future<void> initializeWebviewCallbacks(
|
||||
{required FutureOr<Object> Function(String, BroadcastSenderString)
|
||||
createWebview,
|
||||
required FutureOr<void> Function(Object) openWebview,
|
||||
required FutureOr<void> Function(Object) closeWebview,
|
||||
required FutureOr<String> Function(Object, String) getCookies}) =>
|
||||
RustLib.instance.api.crateApiHostApiWebviewInitializeWebviewCallbacks(
|
||||
createWebview: createWebview,
|
||||
openWebview: openWebview,
|
||||
closeWebview: closeWebview,
|
||||
getCookies: getCookies);
|
||||
|
||||
Future<void> sendWebviewEvents(
|
||||
{required BroadcastSenderString tx, required String event}) =>
|
||||
RustLib.instance.api
|
||||
.crateApiHostApiWebviewSendWebviewEvents(tx: tx, event: event);
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BroadcastSender < String >>>
|
||||
abstract class BroadcastSenderString implements RustOpaqueInterface {}
|
||||
@ -12,6 +12,7 @@ import 'senders.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `create_context`, `js_executor_thread`
|
||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `fmt`
|
||||
// These functions are ignored (category: IgnoreBecauseExplicitAttribute): `open_webview`
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<OpaqueSender>>
|
||||
abstract class OpaqueSender implements RustOpaqueInterface {
|
||||
@ -66,7 +67,7 @@ abstract class SpotubePlugin implements RustOpaqueInterface {
|
||||
|
||||
Future<void> close({required OpaqueSender tx});
|
||||
|
||||
Future<OpaqueSender> createContext(
|
||||
OpaqueSender createContext(
|
||||
{required String pluginScript,
|
||||
required PluginConfiguration pluginConfig});
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@
|
||||
|
||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
||||
|
||||
import 'api/host_api/webview.dart';
|
||||
import 'api/plugin/commands.dart';
|
||||
import 'api/plugin/models/album.dart';
|
||||
import 'api/plugin/models/artist.dart';
|
||||
@ -33,6 +34,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
required super.portManager,
|
||||
});
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_BroadcastSenderStringPtr => wire
|
||||
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderStringPtr;
|
||||
|
||||
CrossPlatformFinalizerArg
|
||||
get rust_arc_decrement_strong_count_OpaqueSenderPtr => wire
|
||||
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSenderPtr;
|
||||
@ -52,6 +57,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
||||
|
||||
@protected
|
||||
BroadcastSenderString
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
OpaqueSender
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
|
||||
@ -92,6 +102,29 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSpotubePlugin(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
FutureOr<void> Function(Object)
|
||||
dco_decode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
FutureOr<String> Function(Object, String)
|
||||
dco_decode_DartFn_Inputs_DartOpaque_String_Output_String_AnyhowException(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
FutureOr<Object> Function(String, BroadcastSenderString)
|
||||
dco_decode_DartFn_Inputs_String_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString_Output_DartOpaque_AnyhowException(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
Object dco_decode_DartOpaque(dynamic raw);
|
||||
|
||||
@protected
|
||||
BroadcastSenderString
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
OpaqueSender
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
|
||||
@ -242,6 +275,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<String> dco_decode_list_String(dynamic raw);
|
||||
|
||||
@ -485,6 +521,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BroadcastSenderString
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
OpaqueSender
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
|
||||
@ -525,6 +566,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSpotubePlugin(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Object sse_decode_DartOpaque(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BroadcastSenderString
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
OpaqueSender
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
|
||||
@ -691,6 +740,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<String> sse_decode_list_String(SseDeserializer deserializer);
|
||||
|
||||
@ -968,6 +1020,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
void sse_encode_AnyhowException(
|
||||
AnyhowException self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
BroadcastSenderString self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
|
||||
@ -1008,6 +1065,28 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSpotubePlugin(
|
||||
SpotubePlugin self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(
|
||||
FutureOr<void> Function(Object) self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_DartFn_Inputs_DartOpaque_String_Output_String_AnyhowException(
|
||||
FutureOr<String> Function(Object, String) self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_String_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString_Output_DartOpaque_AnyhowException(
|
||||
FutureOr<Object> Function(String, BroadcastSenderString) self,
|
||||
SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_DartOpaque(Object self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
BroadcastSenderString self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
|
||||
@ -1170,6 +1249,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_String(List<String> self, SseSerializer serializer);
|
||||
|
||||
@ -1456,6 +1538,38 @@ class RustLibWire implements BaseWire {
|
||||
RustLibWire(ffi.DynamicLibrary dynamicLibrary)
|
||||
: _lookup = dynamicLibrary.lookup;
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderStringPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_spotube_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString');
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString =
|
||||
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderStringPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderStringPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_spotube_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString');
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString =
|
||||
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderStringPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
|
||||
@ -742,7 +742,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
fk_user_agent:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: master
|
||||
@ -1935,13 +1935,13 @@ packages:
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
random_user_agents:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: random_user_agents
|
||||
sha256: "95647149687167e82a7b39e1b4616fdebb574981b71b6f0cfca21b69f36293a8"
|
||||
sha256: "80dc025723a73f04797351aa6ef2fddb14836f86a752711a2f8a04e37c4ccdff"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.17"
|
||||
version: "1.0.18"
|
||||
recase:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -42,6 +42,10 @@ dependencies:
|
||||
envied: ^1.0.0
|
||||
file_picker: 10.3.3
|
||||
file_selector: ^1.0.3
|
||||
fk_user_agent:
|
||||
git:
|
||||
url: https://github.com/TiffApps/fk_user_agent.git
|
||||
ref: master
|
||||
fluentui_system_icons: ^1.1.234
|
||||
flutter:
|
||||
sdk: flutter
|
||||
@ -157,6 +161,7 @@ dependencies:
|
||||
path: rust_builder
|
||||
flutter_rust_bridge: 2.11.1
|
||||
json_annotation: ^4.9.0
|
||||
random_user_agents: ^1.0.18
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.4.13
|
||||
|
||||
99
rust/Cargo.lock
generated
99
rust/Cargo.lock
generated
@ -191,6 +191,26 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
@ -293,6 +313,15 @@ dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
@ -334,6 +363,17 @@ dependencies = [
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.54"
|
||||
@ -898,6 +938,12 @@ version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
@ -1266,6 +1312,15 @@ dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@ -1304,6 +1359,16 @@ version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
@ -1909,6 +1974,12 @@ version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
@ -1939,6 +2010,16 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@ -2175,6 +2256,16 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primefield"
|
||||
version = "0.14.0-rc.1"
|
||||
@ -2396,6 +2487,7 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57b1b6528590d4d65dc86b5159eae2d0219709546644c66408b2441696d1d725"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
]
|
||||
|
||||
@ -2428,6 +2520,7 @@ dependencies = [
|
||||
"flutter_rust_bridge",
|
||||
"heck",
|
||||
"llrt_modules",
|
||||
"once_cell",
|
||||
"rquickjs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -2440,6 +2533,12 @@ version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
|
||||
@ -15,10 +15,11 @@ flutter_rust_bridge = "=2.11.1"
|
||||
anyhow = "1"
|
||||
serde_json = "1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
rquickjs = { version = "0", features = ["chrono", "futures"] }
|
||||
rquickjs = { version = "0", features = ["chrono", "futures", "macro", "classes", "bindgen"] }
|
||||
tokio = { version = "1.48.0", features = ["full"] }
|
||||
heck = "0.5.0"
|
||||
llrt_modules = { git = "https://github.com/awslabs/llrt.git", rev = "7d749dd18cf26a2e51119094c3b945975ae57bd4", features = ["abort", "buffer", "console", "crypto", "events", "exceptions", "fetch", "navigator", "url", "timers"] }
|
||||
once_cell = "1.21.3"
|
||||
|
||||
[patch."https://github.com/DelSkayn/rquickjs"]
|
||||
rquickjs = "0.10.0"
|
||||
|
||||
1
rust/src/api/host_api/mod.rs
Normal file
1
rust/src/api/host_api/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod webview;
|
||||
191
rust/src/api/host_api/webview.rs
Normal file
191
rust/src/api/host_api/webview.rs
Normal file
@ -0,0 +1,191 @@
|
||||
use anyhow::anyhow;
|
||||
use flutter_rust_bridge::for_generated::lazy_static;
|
||||
use flutter_rust_bridge::{frb, DartFnFuture, DartOpaque};
|
||||
use llrt_modules::events::EventEmitter;
|
||||
use rquickjs::function::This;
|
||||
use rquickjs::{Class, Function, Object};
|
||||
use std::sync::Mutex;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
pub type BroadcastSender<T> = broadcast::Sender<T>;
|
||||
pub type BroadcastReceiver<T> = broadcast::Receiver<T>;
|
||||
|
||||
lazy_static! {
|
||||
static ref DART_CREATE_WEBVIEW: Mutex<
|
||||
Option<
|
||||
Box<
|
||||
dyn Fn(String, BroadcastSender<String>) -> DartFnFuture<DartOpaque>
|
||||
+ Send
|
||||
+ 'static,
|
||||
>,
|
||||
>,
|
||||
> = Mutex::new(None);
|
||||
static ref DART_OPEN_WEBVIEW: Mutex<Option<Box<dyn Fn(DartOpaque) -> DartFnFuture<()> + Send + 'static>>> =
|
||||
Mutex::new(None);
|
||||
static ref DART_CLOSE_WEBVIEW: Mutex<Option<Box<dyn Fn(DartOpaque) -> DartFnFuture<()> + Send + 'static>>> =
|
||||
Mutex::new(None);
|
||||
static ref DART_GET_COOKIES: Mutex<Option<Box<dyn Fn(DartOpaque, String) -> DartFnFuture<String> + Send + 'static>>> =
|
||||
Mutex::new(None);
|
||||
}
|
||||
pub async fn initialize_webview_callbacks(
|
||||
create_webview: impl Fn(String, BroadcastSender<String>) -> DartFnFuture<DartOpaque>
|
||||
+ Send
|
||||
+ 'static,
|
||||
open_webview: impl Fn(DartOpaque) -> DartFnFuture<()> + Send + 'static,
|
||||
close_webview: impl Fn(DartOpaque) -> DartFnFuture<()> + Send + 'static,
|
||||
get_cookies: impl Fn(DartOpaque, String) -> DartFnFuture<String> + Send + 'static,
|
||||
) -> anyhow::Result<()> {
|
||||
*DART_CREATE_WEBVIEW
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("Mutex poisoned"))? = Some(Box::new(create_webview));
|
||||
*DART_OPEN_WEBVIEW
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("Mutex poisoned"))? = Some(Box::new(open_webview));
|
||||
*DART_CLOSE_WEBVIEW
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("Mutex poisoned"))? = Some(Box::new(close_webview));
|
||||
*DART_GET_COOKIES
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("Mutex poisoned"))? = Some(Box::new(get_cookies));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_webview_events(tx: BroadcastSender<String>, event: String) -> anyhow::Result<()> {
|
||||
tx.send(event)
|
||||
.map_err(|_| anyhow!("Failed to send event"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[frb(ignore)]
|
||||
pub struct HostWebview {
|
||||
webview: DartOpaque,
|
||||
events: BroadcastReceiver<String>,
|
||||
}
|
||||
|
||||
#[frb(ignore)]
|
||||
impl HostWebview {
|
||||
pub async fn create(uri: String) -> anyhow::Result<Self> {
|
||||
let (tx, rx) = broadcast::channel(100);
|
||||
|
||||
let s = DART_CREATE_WEBVIEW
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("Mutex poisoned"))?;
|
||||
if let Some(create_webview_fn) = s.as_ref() {
|
||||
let s = create_webview_fn(uri, tx.clone()).await;
|
||||
Ok(Self {
|
||||
webview: s,
|
||||
events: rx,
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("create_webview not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn open(&self) -> anyhow::Result<()> {
|
||||
let s = DART_OPEN_WEBVIEW
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("Mutex poisoned"))?;
|
||||
if let Some(open_webview) = s.as_ref() {
|
||||
open_webview(self.webview.clone()).await;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("open_webview not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn close(&self) -> anyhow::Result<()> {
|
||||
let s = DART_CLOSE_WEBVIEW
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("Mutex poisoned"))?;
|
||||
if let Some(close_webview) = s.as_ref() {
|
||||
close_webview(self.webview.clone()).await;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("close_webview not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_cookies(&self, url: String) -> anyhow::Result<String> {
|
||||
let s = DART_GET_COOKIES
|
||||
.lock()
|
||||
.map_err(|_| anyhow!("Mutex poisoned"))?;
|
||||
if let Some(get_cookies) = s.as_ref() {
|
||||
let s = get_cookies(self.webview.clone(), url).await;
|
||||
Ok(s)
|
||||
} else {
|
||||
Err(anyhow!("get_cookies not implemented"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[frb(ignore)]
|
||||
#[rquickjs::class]
|
||||
#[derive(rquickjs::JsLifetime, rquickjs::class::Trace)]
|
||||
pub struct Webview {
|
||||
#[qjs(skip_trace)]
|
||||
webview: HostWebview,
|
||||
}
|
||||
|
||||
#[frb(ignore)]
|
||||
#[rquickjs::methods(rename_all = "camelCase")]
|
||||
impl Webview {
|
||||
#[qjs(static)]
|
||||
pub async fn create(uri: String) -> rquickjs::Result<Self> {
|
||||
let webview = HostWebview::create(uri)
|
||||
.await
|
||||
.map_err(|_| rquickjs::Error::Exception)?;
|
||||
|
||||
Ok(Self { webview })
|
||||
}
|
||||
|
||||
pub async fn open(&mut self, this: This<Class<'_, Self>>) -> rquickjs::Result<()> {
|
||||
let mut events = this.get::<_, Object>("events")?;
|
||||
if events.is_null() || events.is_undefined() {
|
||||
this.set("events", EventEmitter::new())?;
|
||||
events = this.get::<_, Object>("events")?;
|
||||
}
|
||||
|
||||
let emit = events.clone().get::<_, Function>("emit")?;
|
||||
let mut rx = self.webview.events.resubscribe();
|
||||
|
||||
this.ctx().spawn(async move {
|
||||
while let Ok(event) = rx.recv().await {
|
||||
if let Err(e) = emit.call::<_, ()>(("url_change", event)) {
|
||||
eprintln!("Failed to emit event: {:?}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.webview
|
||||
.open()
|
||||
.await
|
||||
.map_err(|_| rquickjs::Error::Exception)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn close(&self) -> rquickjs::Result<()> {
|
||||
self.webview
|
||||
.close()
|
||||
.await
|
||||
.map_err(|_| rquickjs::Error::Exception)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_cookies(&self, url: String) -> rquickjs::Result<String> {
|
||||
self.webview
|
||||
.get_cookies(url)
|
||||
.await
|
||||
.map_err(|_| rquickjs::Error::Exception)
|
||||
}
|
||||
|
||||
pub async fn poll_url_change_event(&mut self) -> rquickjs::Result<String> {
|
||||
let event = self
|
||||
.webview
|
||||
.events
|
||||
.recv()
|
||||
.await
|
||||
.map_err(|_| rquickjs::Error::Exception)?;
|
||||
Ok(event)
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
pub mod plugin;
|
||||
pub mod host_api;
|
||||
|
||||
#[flutter_rust_bridge::frb(init)]
|
||||
pub fn init_app() {
|
||||
|
||||
@ -17,19 +17,26 @@ use llrt_modules::module_builder::ModuleBuilder;
|
||||
use llrt_modules::{
|
||||
abort, buffer, console, crypto, events, exceptions, fetch, navigator, timers, url, util,
|
||||
};
|
||||
use rquickjs::prelude::Func;
|
||||
use rquickjs::{async_with, AsyncContext, AsyncRuntime, Error, Object};
|
||||
use rquickjs::prelude::{Async, Func};
|
||||
use rquickjs::{async_with, AsyncContext, AsyncRuntime, Class, Error, Object};
|
||||
use std::thread;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::task;
|
||||
use tokio::task::LocalSet;
|
||||
use crate::api::host_api::webview::{HostWebview, Webview};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OpaqueSender {
|
||||
pub sender: Sender<PluginCommand>,
|
||||
}
|
||||
|
||||
#[frb(ignore)]
|
||||
pub async fn open_webview(uri: String){
|
||||
let webview = HostWebview::create(uri).await.unwrap();
|
||||
webview.open().await.unwrap();
|
||||
}
|
||||
|
||||
#[frb(ignore)]
|
||||
async fn create_context() -> anyhow::Result<(AsyncContext, AsyncRuntime)> {
|
||||
let runtime = AsyncRuntime::new().expect("Unable to create async runtime");
|
||||
@ -60,6 +67,13 @@ async fn create_context() -> anyhow::Result<(AsyncContext, AsyncRuntime)> {
|
||||
|
||||
async_with!(context => |ctx| {
|
||||
global_attachment.attach(&ctx)?;
|
||||
let global = ctx.globals();
|
||||
Class::<Webview>::define(&global)?;
|
||||
|
||||
|
||||
let globals = ctx.globals();
|
||||
globals.set("openWebview", Func::new(Async(open_webview)))?;
|
||||
|
||||
Ok::<(), Error>(())
|
||||
})
|
||||
.await
|
||||
@ -70,7 +84,7 @@ async fn create_context() -> anyhow::Result<(AsyncContext, AsyncRuntime)> {
|
||||
#[frb(ignore)]
|
||||
async fn js_executor_thread(
|
||||
rx: &mut Receiver<PluginCommand>,
|
||||
ctx: &AsyncContext,
|
||||
context: &AsyncContext,
|
||||
) -> anyhow::Result<()> {
|
||||
while let Some(command) = rx.recv().await {
|
||||
println!("JS Executor thread received command: {:?}", command);
|
||||
@ -79,19 +93,19 @@ async fn js_executor_thread(
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
|
||||
let ctx = ctx.clone();
|
||||
let context = context.clone();
|
||||
task::spawn_local(async move {
|
||||
let result = match command {
|
||||
PluginCommand::Artist(commands) => execute_artists(commands, &ctx).await,
|
||||
PluginCommand::Album(commands) => execute_albums(commands, &ctx).await,
|
||||
PluginCommand::AudioSource(commands) => execute_audio_source(commands, &ctx).await,
|
||||
PluginCommand::Auth(commands) => execute_auth(commands, &ctx).await,
|
||||
PluginCommand::Browse(commands) => execute_browse(commands, &ctx).await,
|
||||
PluginCommand::Core(commands) => execute_core(commands, &ctx).await,
|
||||
PluginCommand::Playlist(commands) => execute_playlist(commands, &ctx).await,
|
||||
PluginCommand::Search(commands) => execute_search(commands, &ctx).await,
|
||||
PluginCommand::Track(commands) => execute_track(commands, &ctx).await,
|
||||
PluginCommand::User(commands) => execute_user(commands, &ctx).await,
|
||||
PluginCommand::Artist(commands) => execute_artists(commands, &context).await,
|
||||
PluginCommand::Album(commands) => execute_albums(commands, &context).await,
|
||||
PluginCommand::AudioSource(commands) => execute_audio_source(commands, &context).await,
|
||||
PluginCommand::Auth(commands) => execute_auth(commands, &context).await,
|
||||
PluginCommand::Browse(commands) => execute_browse(commands, &context).await,
|
||||
PluginCommand::Core(commands) => execute_core(commands, &context).await,
|
||||
PluginCommand::Playlist(commands) => execute_playlist(commands, &context).await,
|
||||
PluginCommand::Search(commands) => execute_search(commands, &context).await,
|
||||
PluginCommand::Track(commands) => execute_track(commands, &context).await,
|
||||
PluginCommand::User(commands) => execute_user(commands, &context).await,
|
||||
PluginCommand::Shutdown => unreachable!(),
|
||||
};
|
||||
|
||||
@ -148,7 +162,7 @@ impl SpotubePlugin {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[frb(sync)]
|
||||
#[frb(sync)]
|
||||
pub fn create_context(
|
||||
&self,
|
||||
plugin_script: String,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
use std::backtrace::Backtrace;
|
||||
use crate::api::plugin::commands::{
|
||||
AlbumCommands, ArtistCommands, AudioSourceCommands, AuthCommands, BrowseCommands, CoreCommands,
|
||||
PlaylistCommands, PluginCommand, SearchCommands, TrackCommands, UserCommands,
|
||||
@ -19,6 +18,7 @@ use crate::api::plugin::models::user::SpotubeUserObject;
|
||||
use crate::api::plugin::plugin::OpaqueSender;
|
||||
use anyhow::anyhow;
|
||||
use flutter_rust_bridge::frb;
|
||||
use std::backtrace::Backtrace;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@ -32,7 +32,7 @@ impl PluginArtistSender {
|
||||
|
||||
pub async fn get_artist(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
) -> anyhow::Result<SpotubeFullArtistObject> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -49,7 +49,7 @@ impl PluginArtistSender {
|
||||
|
||||
pub async fn top_tracks(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -70,7 +70,7 @@ impl PluginArtistSender {
|
||||
|
||||
pub async fn albums(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -91,7 +91,7 @@ impl PluginArtistSender {
|
||||
|
||||
pub async fn related(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -110,7 +110,7 @@ impl PluginArtistSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn save(&self, mpsc_tx: OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
pub async fn save(&self, mpsc_tx: &OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -123,7 +123,7 @@ impl PluginArtistSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn unsave(&self, mpsc_tx: OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
pub async fn unsave(&self, mpsc_tx: &OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -148,7 +148,7 @@ impl PluginAlbumSender {
|
||||
|
||||
pub async fn get_album(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
) -> anyhow::Result<SpotubeFullAlbumObject> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -165,7 +165,7 @@ impl PluginAlbumSender {
|
||||
|
||||
pub async fn tracks(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -186,7 +186,7 @@ impl PluginAlbumSender {
|
||||
|
||||
pub async fn releases(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
) -> anyhow::Result<SpotubePaginationResponseObject> {
|
||||
@ -203,7 +203,7 @@ impl PluginAlbumSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn save(&self, mpsc_tx: OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
pub async fn save(&self, mpsc_tx: &OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -216,7 +216,7 @@ impl PluginAlbumSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn unsave(&self, mpsc_tx: OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
pub async fn unsave(&self, mpsc_tx: &OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -241,7 +241,7 @@ impl PluginAudioSourceSender {
|
||||
|
||||
pub async fn matches(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
track: SpotubeTrackObject,
|
||||
) -> anyhow::Result<Vec<SpotubeAudioSourceMatchObject>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -258,7 +258,7 @@ impl PluginAudioSourceSender {
|
||||
|
||||
pub async fn streams(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
matched: SpotubeAudioSourceMatchObject,
|
||||
) -> anyhow::Result<Vec<SpotubeAudioSourceStreamObject>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -283,7 +283,7 @@ impl PluginAuthSender {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub async fn authenticate(&self, mpsc_tx: OpaqueSender) -> anyhow::Result<()> {
|
||||
pub async fn authenticate(&self, mpsc_tx: &OpaqueSender) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -295,7 +295,7 @@ impl PluginAuthSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn logout(&self, mpsc_tx: OpaqueSender) -> anyhow::Result<()> {
|
||||
pub async fn logout(&self, mpsc_tx: &OpaqueSender) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -307,7 +307,7 @@ impl PluginAuthSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn is_authenticated(&self, mpsc_tx: OpaqueSender) -> anyhow::Result<bool> {
|
||||
pub async fn is_authenticated(&self, mpsc_tx: &OpaqueSender) -> anyhow::Result<bool> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -331,7 +331,7 @@ impl PluginBrowseSender {
|
||||
|
||||
pub async fn sections(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
) -> anyhow::Result<SpotubePaginationResponseObject> {
|
||||
@ -350,7 +350,7 @@ impl PluginBrowseSender {
|
||||
|
||||
pub async fn section_items(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -381,10 +381,9 @@ impl PluginCoreSender {
|
||||
|
||||
pub async fn check_update(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
plugin_config: PluginConfiguration,
|
||||
) -> anyhow::Result<Option<PluginUpdateAvailable>> {
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -394,14 +393,16 @@ impl PluginCoreSender {
|
||||
}))
|
||||
.await?;
|
||||
|
||||
rx.await.map_err(|e| {
|
||||
eprintln!("RecvError: {}", e);
|
||||
eprintln!("Stack trace:\n{:?}", Backtrace::capture());
|
||||
anyhow!("{e}")
|
||||
}).and_then(|o| o)
|
||||
rx.await
|
||||
.map_err(|e| {
|
||||
eprintln!("RecvError: {}", e);
|
||||
eprintln!("Stack trace:\n{:?}", Backtrace::capture());
|
||||
anyhow!("{e}")
|
||||
})
|
||||
.and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn support(&self, mpsc_tx: OpaqueSender) -> anyhow::Result<String> {
|
||||
pub async fn support(&self, mpsc_tx: &OpaqueSender) -> anyhow::Result<String> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -415,7 +416,7 @@ impl PluginCoreSender {
|
||||
|
||||
pub async fn scrobble(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
details: ScrobbleDetails,
|
||||
) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -442,7 +443,7 @@ impl PluginPlaylistSender {
|
||||
|
||||
pub async fn get_playlist(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
) -> anyhow::Result<SpotubeFullPlaylistObject> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -459,7 +460,7 @@ impl PluginPlaylistSender {
|
||||
|
||||
pub async fn tracks(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -480,7 +481,7 @@ impl PluginPlaylistSender {
|
||||
|
||||
pub async fn create_playlist(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
user_id: String,
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
@ -505,7 +506,7 @@ impl PluginPlaylistSender {
|
||||
|
||||
pub async fn update_playlist(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
playlist_id: String,
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
@ -530,7 +531,7 @@ impl PluginPlaylistSender {
|
||||
|
||||
pub async fn delete_playlist(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
playlist_id: String,
|
||||
) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -547,7 +548,7 @@ impl PluginPlaylistSender {
|
||||
|
||||
pub async fn add_tracks(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
playlist_id: String,
|
||||
track_ids: Vec<String>,
|
||||
position: Option<u32>,
|
||||
@ -568,7 +569,7 @@ impl PluginPlaylistSender {
|
||||
|
||||
pub async fn remove_tracks(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
playlist_id: String,
|
||||
track_ids: Vec<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
@ -585,7 +586,7 @@ impl PluginPlaylistSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn save(&self, mpsc_tx: OpaqueSender, playlist_id: String) -> anyhow::Result<()> {
|
||||
pub async fn save(&self, mpsc_tx: &OpaqueSender, playlist_id: String) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -598,7 +599,7 @@ impl PluginPlaylistSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn unsave(&self, mpsc_tx: OpaqueSender, playlist_id: String) -> anyhow::Result<()> {
|
||||
pub async fn unsave(&self, mpsc_tx: &OpaqueSender, playlist_id: String) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -621,7 +622,7 @@ impl PluginSearchSender {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub async fn chips(&self, mpsc_tx: OpaqueSender) -> anyhow::Result<Vec<String>> {
|
||||
pub async fn chips(&self, mpsc_tx: &OpaqueSender) -> anyhow::Result<Vec<String>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -635,7 +636,7 @@ impl PluginSearchSender {
|
||||
|
||||
pub async fn all(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
query: String,
|
||||
) -> anyhow::Result<SpotubeSearchResponseObject> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -652,7 +653,7 @@ impl PluginSearchSender {
|
||||
|
||||
pub async fn tracks(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
query: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -673,7 +674,7 @@ impl PluginSearchSender {
|
||||
|
||||
pub async fn albums(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
query: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -694,7 +695,7 @@ impl PluginSearchSender {
|
||||
|
||||
pub async fn artists(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
query: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -715,7 +716,7 @@ impl PluginSearchSender {
|
||||
|
||||
pub async fn playlists(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
query: String,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
@ -746,7 +747,7 @@ impl PluginTrackSender {
|
||||
|
||||
pub async fn get_track(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
) -> anyhow::Result<SpotubeTrackObject> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -761,7 +762,7 @@ impl PluginTrackSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn save(&self, mpsc_tx: OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
pub async fn save(&self, mpsc_tx: &OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -774,7 +775,7 @@ impl PluginTrackSender {
|
||||
rx.await.map_err(|e| anyhow!("{e}")).and_then(|o| o)
|
||||
}
|
||||
|
||||
pub async fn unsave(&self, mpsc_tx: OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
pub async fn unsave(&self, mpsc_tx: &OpaqueSender, ids: Vec<String>) -> anyhow::Result<()> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -789,7 +790,7 @@ impl PluginTrackSender {
|
||||
|
||||
pub async fn radio(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
id: String,
|
||||
) -> anyhow::Result<Vec<SpotubeTrackObject>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -814,7 +815,7 @@ impl PluginUserSender {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub async fn me(&self, mpsc_tx: OpaqueSender) -> anyhow::Result<SpotubeUserObject> {
|
||||
pub async fn me(&self, mpsc_tx: &OpaqueSender) -> anyhow::Result<SpotubeUserObject> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
mpsc_tx
|
||||
.sender
|
||||
@ -826,7 +827,7 @@ impl PluginUserSender {
|
||||
|
||||
pub async fn saved_tracks(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
) -> anyhow::Result<SpotubePaginationResponseObject> {
|
||||
@ -845,7 +846,7 @@ impl PluginUserSender {
|
||||
|
||||
pub async fn saved_albums(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
) -> anyhow::Result<SpotubePaginationResponseObject> {
|
||||
@ -864,7 +865,7 @@ impl PluginUserSender {
|
||||
|
||||
pub async fn saved_artists(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
) -> anyhow::Result<SpotubePaginationResponseObject> {
|
||||
@ -883,7 +884,7 @@ impl PluginUserSender {
|
||||
|
||||
pub async fn saved_playlists(
|
||||
&self,
|
||||
mpsc_tx: OpaqueSender,
|
||||
mpsc_tx: &OpaqueSender,
|
||||
offset: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
) -> anyhow::Result<SpotubePaginationResponseObject> {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -102,8 +102,8 @@ async fn plugin() -> anyhow::Result<()> {
|
||||
};
|
||||
let sender = plugin.create_context(PLUGIN_JS.to_string(), config.clone())?;
|
||||
let (r1, r2) = tokio::join!(
|
||||
plugin.core.check_update(sender.clone(), config.clone()),
|
||||
plugin.core.check_update(sender.clone(), config.clone())
|
||||
plugin.core.check_update(&sender, config.clone()),
|
||||
plugin.core.check_update(&sender, config.clone())
|
||||
);
|
||||
r1?;
|
||||
r2?;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user