feat: implement webview api for plugin usage

This commit is contained in:
Kingkor Roy Tirtho 2025-12-06 00:31:02 +06:00
parent fca0551032
commit fe83f50286
23 changed files with 1478 additions and 1266 deletions

View File

@ -45,7 +45,6 @@ import 'package:spotube/services/kv_store/encrypted_kv_store.dart';
import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart';
import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/services/wm_tools/wm_tools.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/models/core.dart';
import 'package:spotube/src/rust/api/plugin/plugin.dart'; import 'package:spotube/src/rust/api/plugin/plugin.dart';
import 'package:spotube/src/rust/frb_generated.dart'; import 'package:spotube/src/rust/frb_generated.dart';
@ -99,44 +98,6 @@ Future<void> main(List<String> rawArgs) async {
await KVStoreService.initialize(); await KVStoreService.initialize();
await RustLib.init(); await RustLib.init();
WebViewBinding.register();
final plugin = SpotubePlugin();
const pluginConfiguration = PluginConfiguration(
name: "Spotube Plugin",
description: "Spotube Plugin",
version: "1.0.0",
author: "Spotube",
entryPoint: "Plugin",
pluginApiVersion: "2.0.0",
apis: [PluginApi.localstorage, PluginApi.webview],
abilities: [PluginAbility.metadata],
);
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: pluginContext, pluginConfig: pluginConfiguration);
if (kIsDesktop) { if (kIsDesktop) {
await windowManager.setPreventClose(true); await windowManager.setPreventClose(true);
@ -217,6 +178,58 @@ class Spotube extends HookConsumerWidget {
HomeWidget.registerInteractivityCallback(glanceBackgroundCallback); HomeWidget.registerInteractivityCallback(glanceBackgroundCallback);
} }
start() async {
final server = await ref.read(serverProvider.future);
final plugin = SpotubePlugin();
const pluginConfiguration = PluginConfiguration(
name: "Spotube Plugin",
description: "Spotube Plugin",
version: "1.0.0",
author: "Spotube",
entryPoint: "Plugin",
pluginApiVersion: "2.0.0",
apis: [PluginApi.localstorage, PluginApi.webview],
abilities: [PluginAbility.metadata],
);
final pluginContext = plugin.createContext(
serverEndpointUrl:
"http://${server.server.address.host}:${server.port}",
serverSecret: ref.read(serverRandomSecretProvider),
pluginScript: """
class AuthEndpoint {
}
class CoreEndpoint {
async checkUpdate() {
console.log(globalThis);
const webview = await WebView.create("https://spotube.krtirtho.dev");
webview.onUrlChange((url) => {
console.log("url_request: ", url);
if (url.includes("/about")) {
webview.close();
}
});
await webview.open();
await new Promise((resolve) => setTimeout(resolve, 5000));
}
}
class Plugin {
constructor() {
this.auth = new AuthEndpoint();
this.core = new CoreEndpoint();
}
}
""",
pluginConfig: pluginConfiguration,
);
await plugin.core.checkUpdate(
mpscTx: pluginContext, pluginConfig: pluginConfiguration);
}
start();
return () { return () {
/// For enabling hot reload for audio player /// For enabling hot reload for audio player
if (!kDebugMode) return; if (!kDebugMode) return;

View File

@ -3,10 +3,23 @@ import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart'; import 'package:shelf_router/shelf_router.dart';
import 'package:spotube/provider/server/routes/connect.dart'; import 'package:spotube/provider/server/routes/connect.dart';
import 'package:spotube/provider/server/routes/playback.dart'; import 'package:spotube/provider/server/routes/playback.dart';
import 'package:spotube/provider/server/routes/plugin_apis/webview.dart';
Handler pluginApiAuthMiddleware(Handler handler) {
return (Request request) {
final apiKey = request.headers['X-Plugin-Secret'] ??
request.url.queryParameters['secret'];
if (apiKey == null || apiKey != request.context['plugin_api_secret']) {
return Response.forbidden('Forbidden');
}
return handler(request);
};
}
final serverRouterProvider = Provider((ref) { final serverRouterProvider = Provider((ref) {
final playbackRoutes = ref.watch(serverPlaybackRoutesProvider); final playbackRoutes = ref.watch(serverPlaybackRoutesProvider);
final connectRoutes = ref.watch(serverConnectRoutesProvider); final connectRoutes = ref.watch(serverConnectRoutesProvider);
final webviewRoutes = ref.watch(serverWebviewRoutesProvider);
final router = Router(); final router = Router();
@ -19,7 +32,32 @@ final serverRouterProvider = Provider((ref) {
router.get("/playback/previous", playbackRoutes.previousTrack); router.get("/playback/previous", playbackRoutes.previousTrack);
router.get("/playback/next", playbackRoutes.nextTrack); router.get("/playback/next", playbackRoutes.nextTrack);
router.post(
"/plugin-api/webview/create",
pluginApiAuthMiddleware(webviewRoutes.postCreateWebview),
);
router.get(
"/plugin-api/webview/<uid>/on-url-request",
pluginApiAuthMiddleware(webviewRoutes.getOnUrlRequestStream),
);
router.post(
"/plugin-api/webview/open",
pluginApiAuthMiddleware(webviewRoutes.postOpenWebview),
);
router.post(
"/plugin-api/webview/close",
pluginApiAuthMiddleware(webviewRoutes.postCloseWebview),
);
router.post(
"/plugin-api/webview/cookies",
pluginApiAuthMiddleware(webviewRoutes.postGetWebviewCookies),
);
router.all("/ws", connectRoutes.websocket); router.all("/ws", connectRoutes.websocket);
ref.onDispose(() {
webviewRoutes.dispose();
});
return router; return router;
}); });

View File

@ -0,0 +1,134 @@
import 'dart:async';
import 'dart:convert';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'package:spotube/src/plugin_api/webview/webview.dart';
import 'package:async/async.dart';
class ServerWebviewRoutes {
final Map<String, Webview> _webviews = {};
Future<Response> postCreateWebview(Request request) async {
final payload = jsonDecode(await request.readAsString());
final uri = Uri.parse(payload['url'] as String);
final webview = Webview(uri: uri.toString());
_webviews[webview.uid] = webview;
return Response.ok(
jsonEncode({'uid': webview.uid}),
encoding: utf8,
headers: {
'Content-Type': 'application/json',
},
);
}
Future<Response> getOnUrlRequestStream(Request request) async {
final uid = request.params["uid"];
final webview = _webviews[uid];
if (webview == null) {
return Response.notFound('Webview with uid $uid not found');
}
// Create a stream that merges URL events with keepalive pings
final controller = StreamController<List<int>>();
// Send keepalive comment every 15 seconds to prevent connection timeout
final keepaliveTimer = Stream.periodic(
const Duration(seconds: 15),
(_) => utf8.encode(": keepalive\n\n"),
);
final urlStream = webview.onUrlRequestStream.map((url) {
return utf8.encode("event: url-request\n"
"data: ${jsonEncode({'url': url})}\n\n");
});
// Merge both streams
final subscription = StreamGroup.merge([keepaliveTimer, urlStream]).listen(
(data) {
if (!controller.isClosed) {
controller.add(data);
}
},
onDone: () {
controller.close();
},
);
// Clean up when client disconnects
controller.onCancel = () {
debugPrint('Webview $uid client disconnected');
subscription.cancel();
};
return Response.ok(
controller.stream,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no', // Disable buffering for nginx
},
encoding: utf8,
);
}
Future<Response> postOpenWebview(Request request) async {
final body = jsonDecode(await request.readAsString());
final uid = body['uid'] as String;
final webview = _webviews[uid];
if (webview == null) {
return Response.notFound('Webview with uid $uid not found');
}
await webview.open();
return Response.ok(null);
}
Future<Response> postCloseWebview(Request request) async {
final body = jsonDecode(await request.readAsString());
final uid = body['uid'] as String;
final webview = _webviews[uid];
if (webview == null) {
return Response.notFound('Webview with uid $uid not found');
}
await webview.close();
_webviews.remove(uid);
return Response.ok(null);
}
Future<Response> postGetWebviewCookies(Request request) async {
final body = jsonDecode(await request.readAsString());
final uid = body['uid'] as String;
final url = body['url'] as String;
final webview = _webviews[uid];
if (webview == null) {
return Response.notFound('Webview with uid $uid not found');
}
final cookies = await webview.getCookies(url);
return Response.ok(
jsonEncode(cookies),
encoding: utf8,
headers: {
'Content-Type': 'application/json',
},
);
}
Future<void> dispose() async {
for (final webview in _webviews.values) {
await webview.close();
}
_webviews.clear();
}
}
final serverWebviewRoutesProvider = Provider((ref) => ServerWebviewRoutes());

View File

@ -1,7 +1,9 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart'; import 'package:shelf/shelf_io.dart';
import 'package:spotube/provider/server/pipeline.dart'; import 'package:spotube/provider/server/pipeline.dart';
import 'package:spotube/provider/server/router.dart'; import 'package:spotube/provider/server/router.dart';
@ -9,8 +11,16 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/logger/logger.dart';
final serverRandomSecretProvider = Provider<String>(
(ref) {
final random = Random.secure();
final values = List<int>.generate(16, (i) => random.nextInt(256));
return base64Url.encode(values);
},
);
final serverProvider = FutureProvider( final serverProvider = FutureProvider(
(ref) async { (ref) async {
final randomSecret = ref.watch(serverRandomSecretProvider);
final enabledRemoteConnect = ref.watch( final enabledRemoteConnect = ref.watch(
userPreferencesProvider.select((value) => value.enableConnect), userPreferencesProvider.select((value) => value.enableConnect),
); );
@ -31,8 +41,21 @@ final serverProvider = FutureProvider(
SpotubeMedia.serverPort = connectPort; SpotubeMedia.serverPort = connectPort;
} }
final handler = pipeline.addMiddleware(logRequests()).addMiddleware(
(innerHandler) {
return (request) {
final updatedRequest = request.change(
context: {
'plugin_api_secret': randomSecret,
},
);
return innerHandler(updatedRequest);
};
},
).addHandler(router.call);
final server = await serve( final server = await serve(
pipeline.addHandler(router.call), handler,
enabledRemoteConnect enabledRemoteConnect
? InternetAddress.anyIPv4 ? InternetAddress.anyIPv4
: InternetAddress.loopbackIPv4, : InternetAddress.loopbackIPv4,

View File

@ -22,7 +22,6 @@ class Webview {
required this.uri, required this.uri,
}) : _onUrlRequestStreamController = StreamController<String>.broadcast(), }) : _onUrlRequestStreamController = StreamController<String>.broadcast(),
uid = const Uuid().v4(); uid = const Uuid().v4();
StreamController<String>? _onUrlRequestStreamController; StreamController<String>? _onUrlRequestStreamController;
Stream<String> get onUrlRequestStream => Stream<String> get onUrlRequestStream =>
_onUrlRequestStreamController!.stream; _onUrlRequestStreamController!.stream;

View File

@ -1,40 +0,0 @@
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);
},
);
}
}

View File

@ -1,31 +0,0 @@
// 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 {}

View File

@ -12,7 +12,6 @@ import 'senders.dart';
// These functions are ignored because they are not marked as `pub`: `create_context`, `js_executor_thread` // 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 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>> // Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<OpaqueSender>>
abstract class OpaqueSender implements RustOpaqueInterface { abstract class OpaqueSender implements RustOpaqueInterface {
@ -69,7 +68,9 @@ abstract class SpotubePlugin implements RustOpaqueInterface {
OpaqueSender createContext( OpaqueSender createContext(
{required String pluginScript, {required String pluginScript,
required PluginConfiguration pluginConfig}); required PluginConfiguration pluginConfig,
required String serverEndpointUrl,
required String serverSecret});
factory SpotubePlugin() => factory SpotubePlugin() =>
RustLib.instance.api.crateApiPluginPluginSpotubePluginNew(); RustLib.instance.api.crateApiPluginPluginSpotubePluginNew();

View File

@ -3,7 +3,6 @@
// 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 // 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/commands.dart';
import 'api/plugin/models/album.dart'; import 'api/plugin/models/album.dart';
import 'api/plugin/models/artist.dart'; import 'api/plugin/models/artist.dart';
@ -86,7 +85,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
String get codegenVersion => '2.11.1'; String get codegenVersion => '2.11.1';
@override @override
int get rustContentHash => 1149066488; int get rustContentHash => 1716120288;
static const kDefaultExternalLibraryLoaderConfig = static const kDefaultExternalLibraryLoaderConfig =
ExternalLibraryLoaderConfig( ExternalLibraryLoaderConfig(
@ -174,19 +173,14 @@ abstract class RustLibApi extends BaseApi {
OpaqueSender crateApiPluginPluginSpotubePluginCreateContext( OpaqueSender crateApiPluginPluginSpotubePluginCreateContext(
{required SpotubePlugin that, {required SpotubePlugin that,
required String pluginScript, required String pluginScript,
required PluginConfiguration pluginConfig}); required PluginConfiguration pluginConfig,
required String serverEndpointUrl,
required String serverSecret});
SpotubePlugin crateApiPluginPluginSpotubePluginNew(); SpotubePlugin crateApiPluginPluginSpotubePluginNew();
Future<void> crateApiInitApp(); Future<void> crateApiInitApp();
Future<void> crateApiHostApiWebviewInitializeWebviewCallbacks(
{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});
Future<SpotubeFullAlbumObject> crateApiPluginSendersPluginAlbumSenderGetAlbum( Future<SpotubeFullAlbumObject> crateApiPluginSendersPluginAlbumSenderGetAlbum(
{required PluginAlbumSender that, {required PluginAlbumSender that,
required OpaqueSender mpscTx, required OpaqueSender mpscTx,
@ -463,9 +457,6 @@ abstract class RustLibApi extends BaseApi {
int? offset, int? offset,
int? limit}); int? limit});
Future<void> crateApiHostApiWebviewSendWebviewEvents(
{required BroadcastSenderString tx, required String event});
String String
crateApiPluginModelsAudioSourceSpotubeAudioLosslessContainerQualityToStringFmt( crateApiPluginModelsAudioSourceSpotubeAudioLosslessContainerQualityToStringFmt(
{required SpotubeAudioLosslessContainerQuality that}); {required SpotubeAudioLosslessContainerQuality that});
@ -478,15 +469,6 @@ abstract class RustLibApi extends BaseApi {
crateApiPluginModelsAudioSourceSpotubeAudioSourceContainerPresetFileExtension( crateApiPluginModelsAudioSourceSpotubeAudioSourceContainerPresetFileExtension(
{required SpotubeAudioSourceContainerPreset that}); {required SpotubeAudioSourceContainerPreset that});
RustArcIncrementStrongCountFnType
get rust_arc_increment_strong_count_BroadcastSenderString;
RustArcDecrementStrongCountFnType
get rust_arc_decrement_strong_count_BroadcastSenderString;
CrossPlatformFinalizerArg
get rust_arc_decrement_strong_count_BroadcastSenderStringPtr;
RustArcIncrementStrongCountFnType RustArcIncrementStrongCountFnType
get rust_arc_increment_strong_count_OpaqueSender; get rust_arc_increment_strong_count_OpaqueSender;
@ -1219,7 +1201,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
OpaqueSender crateApiPluginPluginSpotubePluginCreateContext( OpaqueSender crateApiPluginPluginSpotubePluginCreateContext(
{required SpotubePlugin that, {required SpotubePlugin that,
required String pluginScript, required String pluginScript,
required PluginConfiguration pluginConfig}) { required PluginConfiguration pluginConfig,
required String serverEndpointUrl,
required String serverSecret}) {
return handler.executeSync(SyncTask( return handler.executeSync(SyncTask(
callFfi: () { callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding); final serializer = SseSerializer(generalizedFrbRustBinding);
@ -1227,6 +1211,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
that, serializer); that, serializer);
sse_encode_String(pluginScript, serializer); sse_encode_String(pluginScript, serializer);
sse_encode_box_autoadd_plugin_configuration(pluginConfig, serializer); sse_encode_box_autoadd_plugin_configuration(pluginConfig, serializer);
sse_encode_String(serverEndpointUrl, serializer);
sse_encode_String(serverSecret, serializer);
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!; return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!;
}, },
codec: SseCodec( codec: SseCodec(
@ -1235,7 +1221,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
decodeErrorData: sse_decode_AnyhowException, decodeErrorData: sse_decode_AnyhowException,
), ),
constMeta: kCrateApiPluginPluginSpotubePluginCreateContextConstMeta, constMeta: kCrateApiPluginPluginSpotubePluginCreateContextConstMeta,
argValues: [that, pluginScript, pluginConfig], argValues: [
that,
pluginScript,
pluginConfig,
serverEndpointUrl,
serverSecret
],
apiImpl: this, apiImpl: this,
)); ));
} }
@ -1243,7 +1235,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateApiPluginPluginSpotubePluginCreateContextConstMeta => TaskConstMeta get kCrateApiPluginPluginSpotubePluginCreateContextConstMeta =>
const TaskConstMeta( const TaskConstMeta(
debugName: "SpotubePlugin_create_context", debugName: "SpotubePlugin_create_context",
argNames: ["that", "pluginScript", "pluginConfig"], argNames: [
"that",
"pluginScript",
"pluginConfig",
"serverEndpointUrl",
"serverSecret"
],
); );
@override @override
@ -1293,49 +1291,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: [], argNames: [],
); );
@override
Future<void> crateApiHostApiWebviewInitializeWebviewCallbacks(
{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}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_DartFn_Inputs_String_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString_Output_DartOpaque_AnyhowException(
createWebview, serializer);
sse_encode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(
openWebview, serializer);
sse_encode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(
closeWebview, serializer);
sse_encode_DartFn_Inputs_DartOpaque_String_Output_String_AnyhowException(
getCookies, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 28, port: port_);
},
codec: SseCodec(
decodeSuccessData: sse_decode_unit,
decodeErrorData: sse_decode_AnyhowException,
),
constMeta: kCrateApiHostApiWebviewInitializeWebviewCallbacksConstMeta,
argValues: [createWebview, openWebview, closeWebview, getCookies],
apiImpl: this,
));
}
TaskConstMeta
get kCrateApiHostApiWebviewInitializeWebviewCallbacksConstMeta =>
const TaskConstMeta(
debugName: "initialize_webview_callbacks",
argNames: [
"createWebview",
"openWebview",
"closeWebview",
"getCookies"
],
);
@override @override
Future<SpotubeFullAlbumObject> crateApiPluginSendersPluginAlbumSenderGetAlbum( Future<SpotubeFullAlbumObject> crateApiPluginSendersPluginAlbumSenderGetAlbum(
{required PluginAlbumSender that, {required PluginAlbumSender that,
@ -1349,7 +1304,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_String(id, serializer); sse_encode_String(id, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 29, port: port_); funcId: 28, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_full_album_object, decodeSuccessData: sse_decode_spotube_full_album_object,
@ -1383,7 +1338,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 30, port: port_); funcId: 29, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -1414,7 +1369,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_list_String(ids, serializer); sse_encode_list_String(ids, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 31, port: port_); funcId: 30, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -1450,7 +1405,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 32, port: port_); funcId: 31, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -1481,7 +1436,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_list_String(ids, serializer); sse_encode_list_String(ids, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 33, port: port_); funcId: 32, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -1517,7 +1472,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 34, port: port_); funcId: 33, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -1549,7 +1504,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_String(id, serializer); sse_encode_String(id, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 35, port: port_); funcId: 34, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_full_artist_object, decodeSuccessData: sse_decode_spotube_full_artist_object,
@ -1586,7 +1541,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 36, port: port_); funcId: 35, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -1617,7 +1572,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_list_String(ids, serializer); sse_encode_list_String(ids, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 37, port: port_); funcId: 36, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -1653,7 +1608,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 38, port: port_); funcId: 37, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -1685,7 +1640,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_list_String(ids, serializer); sse_encode_list_String(ids, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 39, port: port_); funcId: 38, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -1717,7 +1672,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_box_autoadd_spotube_track_object(track, serializer); sse_encode_box_autoadd_spotube_track_object(track, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 40, port: port_); funcId: 39, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_list_spotube_audio_source_match_object, decodeSuccessData: sse_decode_list_spotube_audio_source_match_object,
@ -1751,7 +1706,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_box_autoadd_spotube_audio_source_match_object( sse_encode_box_autoadd_spotube_audio_source_match_object(
matched, serializer); matched, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 41, port: port_); funcId: 40, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_list_spotube_audio_source_stream_object, decodeSuccessData: sse_decode_list_spotube_audio_source_stream_object,
@ -1780,7 +1735,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
mpscTx, serializer); mpscTx, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 42, port: port_); funcId: 41, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -1809,7 +1764,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
mpscTx, serializer); mpscTx, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 43, port: port_); funcId: 42, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_bool, decodeSuccessData: sse_decode_bool,
@ -1838,7 +1793,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
mpscTx, serializer); mpscTx, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 44, port: port_); funcId: 43, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -1874,7 +1829,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 45, port: port_); funcId: 44, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -1909,7 +1864,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 46, port: port_); funcId: 45, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -1935,7 +1890,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
final serializer = SseSerializer(generalizedFrbRustBinding); final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_box_autoadd_plugin_configuration(that, serializer); sse_encode_box_autoadd_plugin_configuration(that, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 47, port: port_); funcId: 46, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_String, decodeSuccessData: sse_decode_String,
@ -1967,7 +1922,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_box_autoadd_plugin_configuration(pluginConfig, serializer); sse_encode_box_autoadd_plugin_configuration(pluginConfig, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 48, port: port_); funcId: 47, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_opt_box_autoadd_plugin_update_available, decodeSuccessData: sse_decode_opt_box_autoadd_plugin_update_available,
@ -1999,7 +1954,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_box_autoadd_scrobble_details(details, serializer); sse_encode_box_autoadd_scrobble_details(details, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 49, port: port_); funcId: 48, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -2027,7 +1982,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
mpscTx, serializer); mpscTx, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 50, port: port_); funcId: 49, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_String, decodeSuccessData: sse_decode_String,
@ -2062,7 +2017,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_list_String(trackIds, serializer); sse_encode_list_String(trackIds, serializer);
sse_encode_opt_box_autoadd_u_32(position, serializer); sse_encode_opt_box_autoadd_u_32(position, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 51, port: port_); funcId: 50, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -2103,7 +2058,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_bool(public, serializer); sse_encode_opt_box_autoadd_bool(public, serializer);
sse_encode_opt_box_autoadd_bool(collaborative, serializer); sse_encode_opt_box_autoadd_bool(collaborative, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 52, port: port_); funcId: 51, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: decodeSuccessData:
@ -2153,7 +2108,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_String(playlistId, serializer); sse_encode_String(playlistId, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 53, port: port_); funcId: 52, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -2187,7 +2142,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_String(id, serializer); sse_encode_String(id, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 54, port: port_); funcId: 53, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_full_playlist_object, decodeSuccessData: sse_decode_spotube_full_playlist_object,
@ -2221,7 +2176,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_String(playlistId, serializer); sse_encode_String(playlistId, serializer);
sse_encode_list_String(trackIds, serializer); sse_encode_list_String(trackIds, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 55, port: port_); funcId: 54, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -2254,7 +2209,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_String(playlistId, serializer); sse_encode_String(playlistId, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 56, port: port_); funcId: 55, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -2290,7 +2245,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 57, port: port_); funcId: 56, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -2321,7 +2276,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_String(playlistId, serializer); sse_encode_String(playlistId, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 58, port: port_); funcId: 57, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -2360,7 +2315,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_bool(public, serializer); sse_encode_opt_box_autoadd_bool(public, serializer);
sse_encode_opt_box_autoadd_bool(collaborative, serializer); sse_encode_opt_box_autoadd_bool(collaborative, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 59, port: port_); funcId: 58, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -2414,7 +2369,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 60, port: port_); funcId: 59, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -2446,7 +2401,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_String(query, serializer); sse_encode_String(query, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 61, port: port_); funcId: 60, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_search_response_object, decodeSuccessData: sse_decode_spotube_search_response_object,
@ -2482,7 +2437,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 62, port: port_); funcId: 61, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -2510,7 +2465,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
mpscTx, serializer); mpscTx, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 63, port: port_); funcId: 62, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_list_String, decodeSuccessData: sse_decode_list_String,
@ -2546,7 +2501,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 64, port: port_); funcId: 63, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -2583,7 +2538,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 65, port: port_); funcId: 64, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -2614,7 +2569,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_String(id, serializer); sse_encode_String(id, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 66, port: port_); funcId: 65, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_track_object, decodeSuccessData: sse_decode_spotube_track_object,
@ -2645,7 +2600,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_String(id, serializer); sse_encode_String(id, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 67, port: port_); funcId: 66, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_list_spotube_track_object, decodeSuccessData: sse_decode_list_spotube_track_object,
@ -2676,7 +2631,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_list_String(ids, serializer); sse_encode_list_String(ids, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 68, port: port_); funcId: 67, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -2707,7 +2662,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
mpscTx, serializer); mpscTx, serializer);
sse_encode_list_String(ids, serializer); sse_encode_list_String(ids, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 69, port: port_); funcId: 68, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_unit, decodeSuccessData: sse_decode_unit,
@ -2735,7 +2690,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
mpscTx, serializer); mpscTx, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 70, port: port_); funcId: 69, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_user_object, decodeSuccessData: sse_decode_spotube_user_object,
@ -2769,7 +2724,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 71, port: port_); funcId: 70, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -2804,7 +2759,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 72, port: port_); funcId: 71, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -2839,7 +2794,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 73, port: port_); funcId: 72, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -2874,7 +2829,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_opt_box_autoadd_u_32(offset, serializer); sse_encode_opt_box_autoadd_u_32(offset, serializer);
sse_encode_opt_box_autoadd_u_32(limit, serializer); sse_encode_opt_box_autoadd_u_32(limit, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 74, port: port_); funcId: 73, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_spotube_pagination_response_object, decodeSuccessData: sse_decode_spotube_pagination_response_object,
@ -2893,34 +2848,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["that", "mpscTx", "offset", "limit"], argNames: ["that", "mpscTx", "offset", "limit"],
); );
@override
Future<void> crateApiHostApiWebviewSendWebviewEvents(
{required BroadcastSenderString tx, required String event}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
tx, serializer);
sse_encode_String(event, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 75, port: port_);
},
codec: SseCodec(
decodeSuccessData: sse_decode_unit,
decodeErrorData: sse_decode_AnyhowException,
),
constMeta: kCrateApiHostApiWebviewSendWebviewEventsConstMeta,
argValues: [tx, event],
apiImpl: this,
));
}
TaskConstMeta get kCrateApiHostApiWebviewSendWebviewEventsConstMeta =>
const TaskConstMeta(
debugName: "send_webview_events",
argNames: ["tx", "event"],
);
@override @override
String String
crateApiPluginModelsAudioSourceSpotubeAudioLosslessContainerQualityToStringFmt( crateApiPluginModelsAudioSourceSpotubeAudioLosslessContainerQualityToStringFmt(
@ -2930,7 +2857,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
final serializer = SseSerializer(generalizedFrbRustBinding); final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_box_autoadd_spotube_audio_lossless_container_quality( sse_encode_box_autoadd_spotube_audio_lossless_container_quality(
that, serializer); that, serializer);
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 76)!; return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 74)!;
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_String, decodeSuccessData: sse_decode_String,
@ -2960,7 +2887,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_box_autoadd_spotube_audio_lossy_container_quality( sse_encode_box_autoadd_spotube_audio_lossy_container_quality(
that, serializer); that, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer, pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 77, port: port_); funcId: 75, port: port_);
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_String, decodeSuccessData: sse_decode_String,
@ -2989,7 +2916,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
final serializer = SseSerializer(generalizedFrbRustBinding); final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_box_autoadd_spotube_audio_source_container_preset( sse_encode_box_autoadd_spotube_audio_source_container_preset(
that, serializer); that, serializer);
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 78)!; return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 76)!;
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_String, decodeSuccessData: sse_decode_String,
@ -3009,117 +2936,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["that"], argNames: ["that"],
); );
Future<void> Function(int, dynamic)
encode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(
FutureOr<void> Function(Object) raw) {
return (callId, rawArg0) async {
final arg0 = dco_decode_DartOpaque(rawArg0);
Box<void>? rawOutput;
Box<AnyhowException>? rawError;
try {
rawOutput = Box(await raw(arg0));
} catch (e, s) {
rawError = Box(AnyhowException("$e\n\n$s"));
}
final serializer = SseSerializer(generalizedFrbRustBinding);
assert((rawOutput != null) ^ (rawError != null));
if (rawOutput != null) {
serializer.buffer.putUint8(0);
sse_encode_unit(rawOutput.value, serializer);
} else {
serializer.buffer.putUint8(1);
sse_encode_AnyhowException(rawError!.value, serializer);
}
final output = serializer.intoRaw();
generalizedFrbRustBinding.dartFnDeliverOutput(
callId: callId,
ptr: output.ptr,
rustVecLen: output.rustVecLen,
dataLen: output.dataLen);
};
}
Future<void> Function(int, dynamic, dynamic)
encode_DartFn_Inputs_DartOpaque_String_Output_String_AnyhowException(
FutureOr<String> Function(Object, String) raw) {
return (callId, rawArg0, rawArg1) async {
final arg0 = dco_decode_DartOpaque(rawArg0);
final arg1 = dco_decode_String(rawArg1);
Box<String>? rawOutput;
Box<AnyhowException>? rawError;
try {
rawOutput = Box(await raw(arg0, arg1));
} catch (e, s) {
rawError = Box(AnyhowException("$e\n\n$s"));
}
final serializer = SseSerializer(generalizedFrbRustBinding);
assert((rawOutput != null) ^ (rawError != null));
if (rawOutput != null) {
serializer.buffer.putUint8(0);
sse_encode_String(rawOutput.value, serializer);
} else {
serializer.buffer.putUint8(1);
sse_encode_AnyhowException(rawError!.value, serializer);
}
final output = serializer.intoRaw();
generalizedFrbRustBinding.dartFnDeliverOutput(
callId: callId,
ptr: output.ptr,
rustVecLen: output.rustVecLen,
dataLen: output.dataLen);
};
}
Future<void> Function(int, dynamic, dynamic)
encode_DartFn_Inputs_String_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString_Output_DartOpaque_AnyhowException(
FutureOr<Object> Function(String, BroadcastSenderString) raw) {
return (callId, rawArg0, rawArg1) async {
final arg0 = dco_decode_String(rawArg0);
final arg1 =
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
rawArg1);
Box<Object>? rawOutput;
Box<AnyhowException>? rawError;
try {
rawOutput = Box(await raw(arg0, arg1));
} catch (e, s) {
rawError = Box(AnyhowException("$e\n\n$s"));
}
final serializer = SseSerializer(generalizedFrbRustBinding);
assert((rawOutput != null) ^ (rawError != null));
if (rawOutput != null) {
serializer.buffer.putUint8(0);
sse_encode_DartOpaque(rawOutput.value, serializer);
} else {
serializer.buffer.putUint8(1);
sse_encode_AnyhowException(rawError!.value, serializer);
}
final output = serializer.intoRaw();
generalizedFrbRustBinding.dartFnDeliverOutput(
callId: callId,
ptr: output.ptr,
rustVecLen: output.rustVecLen,
dataLen: output.dataLen);
};
}
RustArcIncrementStrongCountFnType
get rust_arc_increment_strong_count_BroadcastSenderString => wire
.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString;
RustArcDecrementStrongCountFnType
get rust_arc_decrement_strong_count_BroadcastSenderString => wire
.rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString;
RustArcIncrementStrongCountFnType RustArcIncrementStrongCountFnType
get rust_arc_increment_strong_count_OpaqueSender => wire get rust_arc_increment_strong_count_OpaqueSender => wire
.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender; .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender;
@ -3158,14 +2974,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return AnyhowException(raw as String); return AnyhowException(raw as String);
} }
@protected
BroadcastSenderString
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return BroadcastSenderStringImpl.frbInternalDcoDecode(raw as List<dynamic>);
}
@protected @protected
OpaqueSender OpaqueSender
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -3230,44 +3038,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return SpotubePluginImpl.frbInternalDcoDecode(raw as List<dynamic>); return SpotubePluginImpl.frbInternalDcoDecode(raw as List<dynamic>);
} }
@protected
FutureOr<void> Function(Object)
dco_decode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(
dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
throw UnimplementedError('');
}
@protected
FutureOr<String> Function(Object, String)
dco_decode_DartFn_Inputs_DartOpaque_String_Output_String_AnyhowException(
dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
throw UnimplementedError('');
}
@protected
FutureOr<Object> Function(String, BroadcastSenderString)
dco_decode_DartFn_Inputs_String_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString_Output_DartOpaque_AnyhowException(
dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
throw UnimplementedError('');
}
@protected
Object dco_decode_DartOpaque(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return decodeDartOpaque(raw, generalizedFrbRustBinding);
}
@protected
BroadcastSenderString
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return BroadcastSenderStringImpl.frbInternalDcoDecode(raw as List<dynamic>);
}
@protected @protected
OpaqueSender OpaqueSender
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -3551,12 +3321,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return dcoDecodeI64(raw); return dcoDecodeI64(raw);
} }
@protected
PlatformInt64 dco_decode_isize(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dcoDecodeI64(raw);
}
@protected @protected
List<String> dco_decode_list_String(dynamic raw) { List<String> dco_decode_list_String(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
@ -4362,15 +4126,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return AnyhowException(inner); return AnyhowException(inner);
} }
@protected
BroadcastSenderString
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return BroadcastSenderStringImpl.frbInternalSseDecode(
sse_decode_usize(deserializer), sse_decode_i_32(deserializer));
}
@protected @protected
OpaqueSender OpaqueSender
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -4443,22 +4198,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_decode_usize(deserializer), sse_decode_i_32(deserializer)); sse_decode_usize(deserializer), sse_decode_i_32(deserializer));
} }
@protected
Object sse_decode_DartOpaque(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var inner = sse_decode_isize(deserializer);
return decodeDartOpaque(inner, generalizedFrbRustBinding);
}
@protected
BroadcastSenderString
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return BroadcastSenderStringImpl.frbInternalSseDecode(
sse_decode_usize(deserializer), sse_decode_i_32(deserializer));
}
@protected @protected
OpaqueSender OpaqueSender
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -4758,12 +4497,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return deserializer.buffer.getPlatformInt64(); return deserializer.buffer.getPlatformInt64();
} }
@protected
PlatformInt64 sse_decode_isize(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return deserializer.buffer.getPlatformInt64();
}
@protected @protected
List<String> sse_decode_list_String(SseDeserializer deserializer) { List<String> sse_decode_list_String(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@ -5761,16 +5494,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_String(self.message, serializer); sse_encode_String(self.message, serializer);
} }
@protected
void
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
BroadcastSenderString self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_usize(
(self as BroadcastSenderStringImpl).frbInternalSseEncode(move: true),
serializer);
}
@protected @protected
void void
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -5851,57 +5574,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
serializer); serializer);
} }
@protected
void sse_encode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(
FutureOr<void> Function(Object) self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_DartOpaque(
encode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(self),
serializer);
}
@protected
void sse_encode_DartFn_Inputs_DartOpaque_String_Output_String_AnyhowException(
FutureOr<String> Function(Object, String) self,
SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_DartOpaque(
encode_DartFn_Inputs_DartOpaque_String_Output_String_AnyhowException(
self),
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) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_DartOpaque(
encode_DartFn_Inputs_String_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString_Output_DartOpaque_AnyhowException(
self),
serializer);
}
@protected
void sse_encode_DartOpaque(Object self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_isize(
PlatformPointerUtil.ptrToPlatformInt64(encodeDartOpaque(
self, portManager.dartHandlerPort, generalizedFrbRustBinding)),
serializer);
}
@protected
void
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
BroadcastSenderString self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_usize(
(self as BroadcastSenderStringImpl).frbInternalSseEncode(move: null),
serializer);
}
@protected @protected
void void
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -6205,12 +5877,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
serializer.buffer.putPlatformInt64(self); serializer.buffer.putPlatformInt64(self);
} }
@protected
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
serializer.buffer.putPlatformInt64(self);
}
@protected @protected
void sse_encode_list_String(List<String> self, SseSerializer serializer) { void sse_encode_list_String(List<String> self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@ -6976,28 +6642,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
} }
} }
@sealed
class BroadcastSenderStringImpl extends RustOpaque
implements BroadcastSenderString {
// Not to be used by end users
BroadcastSenderStringImpl.frbInternalDcoDecode(List<dynamic> wire)
: super.frbInternalDcoDecode(wire, _kStaticData);
// Not to be used by end users
BroadcastSenderStringImpl.frbInternalSseDecode(
BigInt ptr, int externalSizeOnNative)
: super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData);
static final _kStaticData = RustArcStaticData(
rustArcIncrementStrongCount: RustLib
.instance.api.rust_arc_increment_strong_count_BroadcastSenderString,
rustArcDecrementStrongCount: RustLib
.instance.api.rust_arc_decrement_strong_count_BroadcastSenderString,
rustArcDecrementStrongCountPtr: RustLib
.instance.api.rust_arc_decrement_strong_count_BroadcastSenderStringPtr,
);
}
@sealed @sealed
class OpaqueSenderImpl extends RustOpaque implements OpaqueSender { class OpaqueSenderImpl extends RustOpaque implements OpaqueSender {
// Not to be used by end users // Not to be used by end users
@ -7188,7 +6832,13 @@ class SpotubePluginImpl extends RustOpaque implements SpotubePlugin {
OpaqueSender createContext( OpaqueSender createContext(
{required String pluginScript, {required String pluginScript,
required PluginConfiguration pluginConfig}) => required PluginConfiguration pluginConfig,
required String serverEndpointUrl,
required String serverSecret}) =>
RustLib.instance.api.crateApiPluginPluginSpotubePluginCreateContext( RustLib.instance.api.crateApiPluginPluginSpotubePluginCreateContext(
that: this, pluginScript: pluginScript, pluginConfig: pluginConfig); that: this,
pluginScript: pluginScript,
pluginConfig: pluginConfig,
serverEndpointUrl: serverEndpointUrl,
serverSecret: serverSecret);
} }

View File

@ -3,7 +3,6 @@
// 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 // 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/commands.dart';
import 'api/plugin/models/album.dart'; import 'api/plugin/models/album.dart';
import 'api/plugin/models/artist.dart'; import 'api/plugin/models/artist.dart';
@ -34,10 +33,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
required super.portManager, required super.portManager,
}); });
CrossPlatformFinalizerArg
get rust_arc_decrement_strong_count_BroadcastSenderStringPtr => wire
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderStringPtr;
CrossPlatformFinalizerArg CrossPlatformFinalizerArg
get rust_arc_decrement_strong_count_OpaqueSenderPtr => wire get rust_arc_decrement_strong_count_OpaqueSenderPtr => wire
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSenderPtr; ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSenderPtr;
@ -57,11 +52,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
AnyhowException dco_decode_AnyhowException(dynamic raw); AnyhowException dco_decode_AnyhowException(dynamic raw);
@protected
BroadcastSenderString
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
dynamic raw);
@protected @protected
OpaqueSender OpaqueSender
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -102,29 +92,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSpotubePlugin( dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSpotubePlugin(
dynamic raw); 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 @protected
OpaqueSender OpaqueSender
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -275,9 +242,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
PlatformInt64 dco_decode_i_64(dynamic raw); PlatformInt64 dco_decode_i_64(dynamic raw);
@protected
PlatformInt64 dco_decode_isize(dynamic raw);
@protected @protected
List<String> dco_decode_list_String(dynamic raw); List<String> dco_decode_list_String(dynamic raw);
@ -521,11 +485,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
@protected
BroadcastSenderString
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
SseDeserializer deserializer);
@protected @protected
OpaqueSender OpaqueSender
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -566,14 +525,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSpotubePlugin( sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSpotubePlugin(
SseDeserializer deserializer); SseDeserializer deserializer);
@protected
Object sse_decode_DartOpaque(SseDeserializer deserializer);
@protected
BroadcastSenderString
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
SseDeserializer deserializer);
@protected @protected
OpaqueSender OpaqueSender
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -740,9 +691,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
@protected
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
@protected @protected
List<String> sse_decode_list_String(SseDeserializer deserializer); List<String> sse_decode_list_String(SseDeserializer deserializer);
@ -1020,11 +968,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
void sse_encode_AnyhowException( void sse_encode_AnyhowException(
AnyhowException self, SseSerializer serializer); AnyhowException self, SseSerializer serializer);
@protected
void
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
BroadcastSenderString self, SseSerializer serializer);
@protected @protected
void void
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -1065,28 +1008,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSpotubePlugin( sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSpotubePlugin(
SpotubePlugin self, SseSerializer serializer); 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 @protected
void void
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
@ -1249,9 +1170,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
@protected
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
@protected @protected
void sse_encode_list_String(List<String> self, SseSerializer serializer); void sse_encode_list_String(List<String> self, SseSerializer serializer);
@ -1538,38 +1456,6 @@ class RustLibWire implements BaseWire {
RustLibWire(ffi.DynamicLibrary dynamicLibrary) RustLibWire(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup; : _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 void
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
ffi.Pointer<ffi.Void> ptr, ffi.Pointer<ffi.Void> ptr,

718
rust/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,8 @@ rquickjs = { version = "0", features = ["chrono", "futures", "macro", "classes",
tokio = { version = "1.48.0", features = ["full"] } tokio = { version = "1.48.0", features = ["full"] }
heck = "0.5.0" 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"] } 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" eventsource-client = "0.15.1"
reqwest = { version = "0.12", features = ["json"] }
[patch."https://github.com/DelSkayn/rquickjs"] [patch."https://github.com/DelSkayn/rquickjs"]
rquickjs = "0.10.0" rquickjs = "0.10.0"

View File

@ -1 +0,0 @@
pub mod webview;

View File

@ -1,191 +0,0 @@
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)
}
}

View File

@ -1,5 +1,4 @@
pub mod plugin; pub mod plugin;
pub mod host_api;
#[flutter_rust_bridge::frb(init)] #[flutter_rust_bridge::frb(init)]
pub fn init_app() { pub fn init_app() {

View File

@ -11,34 +11,31 @@ use crate::api::plugin::senders::{
PluginTrackSender, PluginUserSender, PluginTrackSender, PluginUserSender,
}; };
use crate::frb_generated::StreamSink; use crate::frb_generated::StreamSink;
use crate::internal::apis::webview;
use anyhow::anyhow; use anyhow::anyhow;
use flutter_rust_bridge::{frb, Rust2DartSendError}; use flutter_rust_bridge::{frb, Rust2DartSendError};
use llrt_modules::module_builder::ModuleBuilder; use llrt_modules::module_builder::ModuleBuilder;
use llrt_modules::{ use llrt_modules::{
abort, buffer, console, crypto, events, exceptions, fetch, navigator, timers, url, util, abort, buffer, console, crypto, events, exceptions, fetch, navigator, timers, url, util,
}; };
use rquickjs::prelude::{Async, Func}; use rquickjs::prelude::Func;
use rquickjs::{async_with, AsyncContext, AsyncRuntime, Class, Error, Object}; use rquickjs::{async_with, AsyncContext, AsyncRuntime, CatchResultExt, Error, Object};
use std::thread; use std::thread;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::mpsc::{Receiver, Sender};
use tokio::task; use tokio::task;
use tokio::task::LocalSet; use tokio::task::LocalSet;
use crate::api::host_api::webview::{HostWebview, Webview};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct OpaqueSender { pub struct OpaqueSender {
pub sender: Sender<PluginCommand>, pub sender: Sender<PluginCommand>,
} }
#[frb(ignore)] // #[frb(ignore)]
pub async fn open_webview(uri: String){ async fn create_context(
let webview = HostWebview::create(uri).await.unwrap(); server_endpoint_url: String,
webview.open().await.unwrap(); server_secret: String,
} ) -> anyhow::Result<(AsyncContext, AsyncRuntime)> {
#[frb(ignore)]
async fn create_context() -> anyhow::Result<(AsyncContext, AsyncRuntime)> {
let runtime = AsyncRuntime::new().expect("Unable to create async runtime"); let runtime = AsyncRuntime::new().expect("Unable to create async runtime");
let mut module_builder = ModuleBuilder::new(); let mut module_builder = ModuleBuilder::new();
@ -66,15 +63,9 @@ async fn create_context() -> anyhow::Result<(AsyncContext, AsyncRuntime)> {
.expect("Unable to create async context"); .expect("Unable to create async context");
async_with!(context => |ctx| { async_with!(context => |ctx| {
global_attachment.attach(&ctx)?; global_attachment.attach(&ctx).catch(&ctx).map_err(|e| anyhow!("Failed to attach global modules: {}", e))?;
let global = ctx.globals(); webview::init(&ctx, server_endpoint_url, server_secret).catch(&ctx).map_err(|e| anyhow!("Failed to initialize WebView API: {}", e))?;
Class::<Webview>::define(&global)?; anyhow::Ok(())
let globals = ctx.globals();
globals.set("openWebview", Func::new(Async(open_webview)))?;
Ok::<(), Error>(())
}) })
.await .await
.map_err(|e| anyhow!("Failed to register globals: {}", e))?; .map_err(|e| anyhow!("Failed to register globals: {}", e))?;
@ -98,7 +89,9 @@ async fn js_executor_thread(
let result = match command { let result = match command {
PluginCommand::Artist(commands) => execute_artists(commands, &context).await, PluginCommand::Artist(commands) => execute_artists(commands, &context).await,
PluginCommand::Album(commands) => execute_albums(commands, &context).await, PluginCommand::Album(commands) => execute_albums(commands, &context).await,
PluginCommand::AudioSource(commands) => execute_audio_source(commands, &context).await, PluginCommand::AudioSource(commands) => {
execute_audio_source(commands, &context).await
}
PluginCommand::Auth(commands) => execute_auth(commands, &context).await, PluginCommand::Auth(commands) => execute_auth(commands, &context).await,
PluginCommand::Browse(commands) => execute_browse(commands, &context).await, PluginCommand::Browse(commands) => execute_browse(commands, &context).await,
PluginCommand::Core(commands) => execute_core(commands, &context).await, PluginCommand::Core(commands) => execute_core(commands, &context).await,
@ -162,11 +155,13 @@ impl SpotubePlugin {
Ok(()) Ok(())
} }
#[frb(sync)] // #[frb(sync)]
pub fn create_context( pub fn create_context(
&self, &self,
plugin_script: String, plugin_script: String,
plugin_config: PluginConfiguration, plugin_config: PluginConfiguration,
server_endpoint_url: String,
server_secret: String,
) -> anyhow::Result<OpaqueSender> { ) -> anyhow::Result<OpaqueSender> {
let (command_tx, mut command_rx) = mpsc::channel(32); let (command_tx, mut command_rx) = mpsc::channel(32);
let sender = self.event_tx.clone(); let sender = self.event_tx.clone();
@ -177,7 +172,10 @@ impl SpotubePlugin {
.unwrap(); .unwrap();
let local = LocalSet::new(); let local = LocalSet::new();
if let Err(e) = local.block_on(&rt, async { if let Err(e) = local.block_on(&rt, async {
let (ctx, _) = create_context().await?; let (ctx, _) = create_context(
server_endpoint_url,
server_secret,
).await?;
let injection = format!( let injection = format!(
"globalThis.pluginInstance = new {}();", "globalThis.pluginInstance = new {}();",
@ -185,7 +183,10 @@ impl SpotubePlugin {
); );
let script = format!("{}\n{}", plugin_script, injection); let script = format!("{}\n{}", plugin_script, injection);
ctx.with(|cx| cx.eval::<(), _>(script.as_str())).await?; async_with!(ctx => |cx| {
cx.eval::<(), _>(script.as_str())
.catch(&cx).map_err(|e| anyhow!("Failed to evaluate supplied plugin script: {}", e))
}).await?;
async_with!(ctx => |ctx|{ async_with!(ctx => |ctx|{
let globals = ctx.globals(); let globals = ctx.globals();

View File

@ -25,7 +25,6 @@
// Section: imports // Section: imports
use crate::api::host_api::webview::*;
use crate::api::plugin::commands::*; use crate::api::plugin::commands::*;
use crate::api::plugin::plugin::*; use crate::api::plugin::plugin::*;
use crate::*; use crate::*;
@ -43,7 +42,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueMoi, default_rust_auto_opaque = RustAutoOpaqueMoi,
); );
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1149066488; pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1716120288;
// Section: executor // Section: executor
@ -1282,6 +1281,8 @@ fn wire__crate__api__plugin__plugin__SpotubePlugin_create_context_impl(
<crate::api::plugin::models::core::PluginConfiguration>::sse_decode( <crate::api::plugin::models::core::PluginConfiguration>::sse_decode(
&mut deserializer, &mut deserializer,
); );
let api_server_endpoint_url = <String>::sse_decode(&mut deserializer);
let api_server_secret = <String>::sse_decode(&mut deserializer);
deserializer.end(); deserializer.end();
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || { (move || {
@ -1303,6 +1304,8 @@ fn wire__crate__api__plugin__plugin__SpotubePlugin_create_context_impl(
&*api_that_guard, &*api_that_guard,
api_plugin_script, api_plugin_script,
api_plugin_config, api_plugin_config,
api_server_endpoint_url,
api_server_secret,
)?; )?;
Ok(output_ok) Ok(output_ok)
})(), })(),
@ -1374,24 +1377,6 @@ fn wire__crate__api__init_app_impl(
}, },
) )
} }
fn wire__crate__api__host_api__webview__initialize_webview_callbacks_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
data_len_: i32,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "initialize_webview_callbacks", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || {
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_create_webview = decode_DartFn_Inputs_String_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString_Output_DartOpaque_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_open_webview = decode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_close_webview = decode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_get_cookies = decode_DartFn_Inputs_DartOpaque_String_Output_String_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));deserializer.end(); move |context| async move {
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move {
let output_ok = crate::api::host_api::webview::initialize_webview_callbacks(api_create_webview, api_open_webview, api_close_webview, api_get_cookies).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__api__plugin__senders__plugin_album_sender_get_album_impl( fn wire__crate__api__plugin__senders__plugin_album_sender_get_album_impl(
port_: flutter_rust_bridge::for_generated::MessagePort, port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
@ -4401,45 +4386,6 @@ fn wire__crate__api__plugin__senders__plugin_user_sender_saved_tracks_impl(
}, },
) )
} }
fn wire__crate__api__host_api__webview__send_webview_events_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
data_len_: i32,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "send_webview_events",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let message = unsafe {
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
ptr_,
rust_vec_len_,
data_len_,
)
};
let mut deserializer =
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_tx = <BroadcastSender<String>>::sse_decode(&mut deserializer);
let api_event = <String>::sse_decode(&mut deserializer);
deserializer.end();
move |context| async move {
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || async move {
let output_ok =
crate::api::host_api::webview::send_webview_events(api_tx, api_event)
.await?;
Ok(output_ok)
})()
.await,
)
}
},
)
}
fn wire__crate__api__plugin__models__audio_source__spotube_audio_lossless_container_quality_to_string_fmt_impl( fn wire__crate__api__plugin__models__audio_source__spotube_audio_lossless_container_quality_to_string_fmt_impl(
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32, rust_vec_len_: i32,
@ -4484,129 +4430,6 @@ fn wire__crate__api__plugin__models__audio_source__spotube_audio_source_containe
// Section: related_funcs // Section: related_funcs
fn decode_DartFn_Inputs_DartOpaque_Output_unit_AnyhowException(
dart_opaque: flutter_rust_bridge::DartOpaque,
) -> impl Fn(flutter_rust_bridge::DartOpaque) -> flutter_rust_bridge::DartFnFuture<()> {
use flutter_rust_bridge::IntoDart;
async fn body(
dart_opaque: flutter_rust_bridge::DartOpaque,
arg0: flutter_rust_bridge::DartOpaque,
) -> () {
let args = vec![arg0.into_into_dart().into_dart()];
let message = FLUTTER_RUST_BRIDGE_HANDLER
.dart_fn_invoke(dart_opaque, args)
.await;
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let action = deserializer.cursor.read_u8().unwrap();
let ans = match action {
0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)),
1 => std::result::Result::Err(
<flutter_rust_bridge::for_generated::anyhow::Error>::sse_decode(&mut deserializer),
),
_ => unreachable!(),
};
deserializer.end();
let ans = ans.expect("Dart throws exception but Rust side assume it is not failable");
ans
}
move |arg0: flutter_rust_bridge::DartOpaque| {
flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(
dart_opaque.clone(),
arg0,
))
}
}
fn decode_DartFn_Inputs_DartOpaque_String_Output_String_AnyhowException(
dart_opaque: flutter_rust_bridge::DartOpaque,
) -> impl Fn(flutter_rust_bridge::DartOpaque, String) -> flutter_rust_bridge::DartFnFuture<String> {
use flutter_rust_bridge::IntoDart;
async fn body(
dart_opaque: flutter_rust_bridge::DartOpaque,
arg0: flutter_rust_bridge::DartOpaque,
arg1: String,
) -> String {
let args = vec![
arg0.into_into_dart().into_dart(),
arg1.into_into_dart().into_dart(),
];
let message = FLUTTER_RUST_BRIDGE_HANDLER
.dart_fn_invoke(dart_opaque, args)
.await;
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let action = deserializer.cursor.read_u8().unwrap();
let ans = match action {
0 => std::result::Result::Ok(<String>::sse_decode(&mut deserializer)),
1 => std::result::Result::Err(
<flutter_rust_bridge::for_generated::anyhow::Error>::sse_decode(&mut deserializer),
),
_ => unreachable!(),
};
deserializer.end();
let ans = ans.expect("Dart throws exception but Rust side assume it is not failable");
ans
}
move |arg0: flutter_rust_bridge::DartOpaque, arg1: String| {
flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(
dart_opaque.clone(),
arg0,
arg1,
))
}
}
fn decode_DartFn_Inputs_String_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString_Output_DartOpaque_AnyhowException(
dart_opaque: flutter_rust_bridge::DartOpaque,
) -> impl Fn(
String,
BroadcastSender<String>,
) -> flutter_rust_bridge::DartFnFuture<flutter_rust_bridge::DartOpaque> {
use flutter_rust_bridge::IntoDart;
async fn body(
dart_opaque: flutter_rust_bridge::DartOpaque,
arg0: String,
arg1: BroadcastSender<String>,
) -> flutter_rust_bridge::DartOpaque {
let args = vec![
arg0.into_into_dart().into_dart(),
arg1.into_into_dart().into_dart(),
];
let message = FLUTTER_RUST_BRIDGE_HANDLER
.dart_fn_invoke(dart_opaque, args)
.await;
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let action = deserializer.cursor.read_u8().unwrap();
let ans = match action {
0 => std::result::Result::Ok(<flutter_rust_bridge::DartOpaque>::sse_decode(
&mut deserializer,
)),
1 => std::result::Result::Err(
<flutter_rust_bridge::for_generated::anyhow::Error>::sse_decode(&mut deserializer),
),
_ => unreachable!(),
};
deserializer.end();
let ans = ans.expect("Dart throws exception but Rust side assume it is not failable");
ans
}
move |arg0: String, arg1: BroadcastSender<String>| {
flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(
dart_opaque.clone(),
arg0,
arg1,
))
}
}
flutter_rust_bridge::frb_generated_moi_arc_impl_value!(
flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BroadcastSender<String>>
);
flutter_rust_bridge::frb_generated_moi_arc_impl_value!( flutter_rust_bridge::frb_generated_moi_arc_impl_value!(
flutter_rust_bridge::for_generated::RustAutoOpaqueInner<OpaqueSender> flutter_rust_bridge::for_generated::RustAutoOpaqueInner<OpaqueSender>
); );
@ -4630,16 +4453,6 @@ impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error {
} }
} }
impl SseDecode for BroadcastSender<String> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut inner = <RustOpaqueMoi<
flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BroadcastSender<String>>,
>>::sse_decode(deserializer);
return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner);
}
}
impl SseDecode for OpaqueSender { impl SseDecode for OpaqueSender {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@ -4680,26 +4493,6 @@ impl SseDecode for SpotubePlugin {
} }
} }
impl SseDecode for flutter_rust_bridge::DartOpaque {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut inner = <usize>::sse_decode(deserializer);
return unsafe { flutter_rust_bridge::for_generated::sse_decode_dart_opaque(inner) };
}
}
impl SseDecode
for RustOpaqueMoi<
flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BroadcastSender<String>>,
>
{
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut inner = <usize>::sse_decode(deserializer);
return decode_rust_opaque_moi(inner);
}
}
impl SseDecode impl SseDecode
for RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<OpaqueSender>> for RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<OpaqueSender>>
{ {
@ -4815,13 +4608,6 @@ impl SseDecode for i64 {
} }
} }
impl SseDecode for isize {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
deserializer.cursor.read_i64::<NativeEndian>().unwrap() as _
}
}
impl SseDecode for Vec<String> { impl SseDecode for Vec<String> {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@ -5970,55 +5756,53 @@ fn pde_ffi_dispatcher_primary_impl(
3 => wire__crate__api__plugin__plugin__SpotubePlugin_auth_state_impl(port, ptr, rust_vec_len, data_len), 3 => wire__crate__api__plugin__plugin__SpotubePlugin_auth_state_impl(port, ptr, rust_vec_len, data_len),
24 => wire__crate__api__plugin__plugin__SpotubePlugin_close_impl(port, ptr, rust_vec_len, data_len), 24 => wire__crate__api__plugin__plugin__SpotubePlugin_close_impl(port, ptr, rust_vec_len, data_len),
27 => wire__crate__api__init_app_impl(port, ptr, rust_vec_len, data_len), 27 => wire__crate__api__init_app_impl(port, ptr, rust_vec_len, data_len),
28 => wire__crate__api__host_api__webview__initialize_webview_callbacks_impl(port, ptr, rust_vec_len, data_len), 28 => wire__crate__api__plugin__senders__plugin_album_sender_get_album_impl(port, ptr, rust_vec_len, data_len),
29 => wire__crate__api__plugin__senders__plugin_album_sender_get_album_impl(port, ptr, rust_vec_len, data_len), 29 => wire__crate__api__plugin__senders__plugin_album_sender_releases_impl(port, ptr, rust_vec_len, data_len),
30 => wire__crate__api__plugin__senders__plugin_album_sender_releases_impl(port, ptr, rust_vec_len, data_len), 30 => wire__crate__api__plugin__senders__plugin_album_sender_save_impl(port, ptr, rust_vec_len, data_len),
31 => wire__crate__api__plugin__senders__plugin_album_sender_save_impl(port, ptr, rust_vec_len, data_len), 31 => wire__crate__api__plugin__senders__plugin_album_sender_tracks_impl(port, ptr, rust_vec_len, data_len),
32 => wire__crate__api__plugin__senders__plugin_album_sender_tracks_impl(port, ptr, rust_vec_len, data_len), 32 => wire__crate__api__plugin__senders__plugin_album_sender_unsave_impl(port, ptr, rust_vec_len, data_len),
33 => wire__crate__api__plugin__senders__plugin_album_sender_unsave_impl(port, ptr, rust_vec_len, data_len), 33 => wire__crate__api__plugin__senders__plugin_artist_sender_albums_impl(port, ptr, rust_vec_len, data_len),
34 => wire__crate__api__plugin__senders__plugin_artist_sender_albums_impl(port, ptr, rust_vec_len, data_len), 34 => wire__crate__api__plugin__senders__plugin_artist_sender_get_artist_impl(port, ptr, rust_vec_len, data_len),
35 => wire__crate__api__plugin__senders__plugin_artist_sender_get_artist_impl(port, ptr, rust_vec_len, data_len), 35 => wire__crate__api__plugin__senders__plugin_artist_sender_related_impl(port, ptr, rust_vec_len, data_len),
36 => wire__crate__api__plugin__senders__plugin_artist_sender_related_impl(port, ptr, rust_vec_len, data_len), 36 => wire__crate__api__plugin__senders__plugin_artist_sender_save_impl(port, ptr, rust_vec_len, data_len),
37 => wire__crate__api__plugin__senders__plugin_artist_sender_save_impl(port, ptr, rust_vec_len, data_len), 37 => wire__crate__api__plugin__senders__plugin_artist_sender_top_tracks_impl(port, ptr, rust_vec_len, data_len),
38 => wire__crate__api__plugin__senders__plugin_artist_sender_top_tracks_impl(port, ptr, rust_vec_len, data_len), 38 => wire__crate__api__plugin__senders__plugin_artist_sender_unsave_impl(port, ptr, rust_vec_len, data_len),
39 => wire__crate__api__plugin__senders__plugin_artist_sender_unsave_impl(port, ptr, rust_vec_len, data_len), 39 => wire__crate__api__plugin__senders__plugin_audio_source_sender_matches_impl(port, ptr, rust_vec_len, data_len),
40 => wire__crate__api__plugin__senders__plugin_audio_source_sender_matches_impl(port, ptr, rust_vec_len, data_len), 40 => wire__crate__api__plugin__senders__plugin_audio_source_sender_streams_impl(port, ptr, rust_vec_len, data_len),
41 => wire__crate__api__plugin__senders__plugin_audio_source_sender_streams_impl(port, ptr, rust_vec_len, data_len), 41 => wire__crate__api__plugin__senders__plugin_auth_sender_authenticate_impl(port, ptr, rust_vec_len, data_len),
42 => wire__crate__api__plugin__senders__plugin_auth_sender_authenticate_impl(port, ptr, rust_vec_len, data_len), 42 => wire__crate__api__plugin__senders__plugin_auth_sender_is_authenticated_impl(port, ptr, rust_vec_len, data_len),
43 => wire__crate__api__plugin__senders__plugin_auth_sender_is_authenticated_impl(port, ptr, rust_vec_len, data_len), 43 => wire__crate__api__plugin__senders__plugin_auth_sender_logout_impl(port, ptr, rust_vec_len, data_len),
44 => wire__crate__api__plugin__senders__plugin_auth_sender_logout_impl(port, ptr, rust_vec_len, data_len), 44 => wire__crate__api__plugin__senders__plugin_browse_sender_section_items_impl(port, ptr, rust_vec_len, data_len),
45 => wire__crate__api__plugin__senders__plugin_browse_sender_section_items_impl(port, ptr, rust_vec_len, data_len), 45 => wire__crate__api__plugin__senders__plugin_browse_sender_sections_impl(port, ptr, rust_vec_len, data_len),
46 => wire__crate__api__plugin__senders__plugin_browse_sender_sections_impl(port, ptr, rust_vec_len, data_len), 46 => wire__crate__api__plugin__models__core__plugin_configuration_slug_impl(port, ptr, rust_vec_len, data_len),
47 => wire__crate__api__plugin__models__core__plugin_configuration_slug_impl(port, ptr, rust_vec_len, data_len), 47 => wire__crate__api__plugin__senders__plugin_core_sender_check_update_impl(port, ptr, rust_vec_len, data_len),
48 => wire__crate__api__plugin__senders__plugin_core_sender_check_update_impl(port, ptr, rust_vec_len, data_len), 48 => wire__crate__api__plugin__senders__plugin_core_sender_scrobble_impl(port, ptr, rust_vec_len, data_len),
49 => wire__crate__api__plugin__senders__plugin_core_sender_scrobble_impl(port, ptr, rust_vec_len, data_len), 49 => wire__crate__api__plugin__senders__plugin_core_sender_support_impl(port, ptr, rust_vec_len, data_len),
50 => wire__crate__api__plugin__senders__plugin_core_sender_support_impl(port, ptr, rust_vec_len, data_len), 50 => wire__crate__api__plugin__senders__plugin_playlist_sender_add_tracks_impl(port, ptr, rust_vec_len, data_len),
51 => wire__crate__api__plugin__senders__plugin_playlist_sender_add_tracks_impl(port, ptr, rust_vec_len, data_len), 51 => wire__crate__api__plugin__senders__plugin_playlist_sender_create_playlist_impl(port, ptr, rust_vec_len, data_len),
52 => wire__crate__api__plugin__senders__plugin_playlist_sender_create_playlist_impl(port, ptr, rust_vec_len, data_len), 52 => wire__crate__api__plugin__senders__plugin_playlist_sender_delete_playlist_impl(port, ptr, rust_vec_len, data_len),
53 => wire__crate__api__plugin__senders__plugin_playlist_sender_delete_playlist_impl(port, ptr, rust_vec_len, data_len), 53 => wire__crate__api__plugin__senders__plugin_playlist_sender_get_playlist_impl(port, ptr, rust_vec_len, data_len),
54 => wire__crate__api__plugin__senders__plugin_playlist_sender_get_playlist_impl(port, ptr, rust_vec_len, data_len), 54 => wire__crate__api__plugin__senders__plugin_playlist_sender_remove_tracks_impl(port, ptr, rust_vec_len, data_len),
55 => wire__crate__api__plugin__senders__plugin_playlist_sender_remove_tracks_impl(port, ptr, rust_vec_len, data_len), 55 => wire__crate__api__plugin__senders__plugin_playlist_sender_save_impl(port, ptr, rust_vec_len, data_len),
56 => wire__crate__api__plugin__senders__plugin_playlist_sender_save_impl(port, ptr, rust_vec_len, data_len), 56 => wire__crate__api__plugin__senders__plugin_playlist_sender_tracks_impl(port, ptr, rust_vec_len, data_len),
57 => wire__crate__api__plugin__senders__plugin_playlist_sender_tracks_impl(port, ptr, rust_vec_len, data_len), 57 => wire__crate__api__plugin__senders__plugin_playlist_sender_unsave_impl(port, ptr, rust_vec_len, data_len),
58 => wire__crate__api__plugin__senders__plugin_playlist_sender_unsave_impl(port, ptr, rust_vec_len, data_len), 58 => wire__crate__api__plugin__senders__plugin_playlist_sender_update_playlist_impl(port, ptr, rust_vec_len, data_len),
59 => wire__crate__api__plugin__senders__plugin_playlist_sender_update_playlist_impl(port, ptr, rust_vec_len, data_len), 59 => wire__crate__api__plugin__senders__plugin_search_sender_albums_impl(port, ptr, rust_vec_len, data_len),
60 => wire__crate__api__plugin__senders__plugin_search_sender_albums_impl(port, ptr, rust_vec_len, data_len), 60 => wire__crate__api__plugin__senders__plugin_search_sender_all_impl(port, ptr, rust_vec_len, data_len),
61 => wire__crate__api__plugin__senders__plugin_search_sender_all_impl(port, ptr, rust_vec_len, data_len), 61 => wire__crate__api__plugin__senders__plugin_search_sender_artists_impl(port, ptr, rust_vec_len, data_len),
62 => wire__crate__api__plugin__senders__plugin_search_sender_artists_impl(port, ptr, rust_vec_len, data_len), 62 => wire__crate__api__plugin__senders__plugin_search_sender_chips_impl(port, ptr, rust_vec_len, data_len),
63 => wire__crate__api__plugin__senders__plugin_search_sender_chips_impl(port, ptr, rust_vec_len, data_len), 63 => wire__crate__api__plugin__senders__plugin_search_sender_playlists_impl(port, ptr, rust_vec_len, data_len),
64 => wire__crate__api__plugin__senders__plugin_search_sender_playlists_impl(port, ptr, rust_vec_len, data_len), 64 => wire__crate__api__plugin__senders__plugin_search_sender_tracks_impl(port, ptr, rust_vec_len, data_len),
65 => wire__crate__api__plugin__senders__plugin_search_sender_tracks_impl(port, ptr, rust_vec_len, data_len), 65 => wire__crate__api__plugin__senders__plugin_track_sender_get_track_impl(port, ptr, rust_vec_len, data_len),
66 => wire__crate__api__plugin__senders__plugin_track_sender_get_track_impl(port, ptr, rust_vec_len, data_len), 66 => wire__crate__api__plugin__senders__plugin_track_sender_radio_impl(port, ptr, rust_vec_len, data_len),
67 => wire__crate__api__plugin__senders__plugin_track_sender_radio_impl(port, ptr, rust_vec_len, data_len), 67 => wire__crate__api__plugin__senders__plugin_track_sender_save_impl(port, ptr, rust_vec_len, data_len),
68 => wire__crate__api__plugin__senders__plugin_track_sender_save_impl(port, ptr, rust_vec_len, data_len), 68 => wire__crate__api__plugin__senders__plugin_track_sender_unsave_impl(port, ptr, rust_vec_len, data_len),
69 => wire__crate__api__plugin__senders__plugin_track_sender_unsave_impl(port, ptr, rust_vec_len, data_len), 69 => wire__crate__api__plugin__senders__plugin_user_sender_me_impl(port, ptr, rust_vec_len, data_len),
70 => wire__crate__api__plugin__senders__plugin_user_sender_me_impl(port, ptr, rust_vec_len, data_len), 70 => wire__crate__api__plugin__senders__plugin_user_sender_saved_albums_impl(port, ptr, rust_vec_len, data_len),
71 => wire__crate__api__plugin__senders__plugin_user_sender_saved_albums_impl(port, ptr, rust_vec_len, data_len), 71 => wire__crate__api__plugin__senders__plugin_user_sender_saved_artists_impl(port, ptr, rust_vec_len, data_len),
72 => wire__crate__api__plugin__senders__plugin_user_sender_saved_artists_impl(port, ptr, rust_vec_len, data_len), 72 => wire__crate__api__plugin__senders__plugin_user_sender_saved_playlists_impl(port, ptr, rust_vec_len, data_len),
73 => wire__crate__api__plugin__senders__plugin_user_sender_saved_playlists_impl(port, ptr, rust_vec_len, data_len), 73 => wire__crate__api__plugin__senders__plugin_user_sender_saved_tracks_impl(port, ptr, rust_vec_len, data_len),
74 => wire__crate__api__plugin__senders__plugin_user_sender_saved_tracks_impl(port, ptr, rust_vec_len, data_len), 75 => wire__crate__api__plugin__models__audio_source__spotube_audio_lossy_container_quality_to_string_fmt_impl(port, ptr, rust_vec_len, data_len),
75 => wire__crate__api__host_api__webview__send_webview_events_impl(port, ptr, rust_vec_len, data_len),
77 => wire__crate__api__plugin__models__audio_source__spotube_audio_lossy_container_quality_to_string_fmt_impl(port, ptr, rust_vec_len, data_len),
_ => unreachable!(), _ => unreachable!(),
} }
} }
@ -6055,34 +5839,14 @@ fn pde_ffi_dispatcher_sync_impl(
23 => wire__crate__api__plugin__plugin__SpotubePlugin_auto_accessor_set_user_impl(ptr, rust_vec_len, data_len), 23 => wire__crate__api__plugin__plugin__SpotubePlugin_auto_accessor_set_user_impl(ptr, rust_vec_len, data_len),
25 => wire__crate__api__plugin__plugin__SpotubePlugin_create_context_impl(ptr, rust_vec_len, data_len), 25 => wire__crate__api__plugin__plugin__SpotubePlugin_create_context_impl(ptr, rust_vec_len, data_len),
26 => wire__crate__api__plugin__plugin__SpotubePlugin_new_impl(ptr, rust_vec_len, data_len), 26 => wire__crate__api__plugin__plugin__SpotubePlugin_new_impl(ptr, rust_vec_len, data_len),
76 => wire__crate__api__plugin__models__audio_source__spotube_audio_lossless_container_quality_to_string_fmt_impl(ptr, rust_vec_len, data_len), 74 => wire__crate__api__plugin__models__audio_source__spotube_audio_lossless_container_quality_to_string_fmt_impl(ptr, rust_vec_len, data_len),
78 => wire__crate__api__plugin__models__audio_source__spotube_audio_source_container_preset_file_extension_impl(ptr, rust_vec_len, data_len), 76 => wire__crate__api__plugin__models__audio_source__spotube_audio_source_container_preset_file_extension_impl(ptr, rust_vec_len, data_len),
_ => unreachable!(), _ => unreachable!(),
} }
} }
// Section: rust2dart // Section: rust2dart
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for FrbWrapper<BroadcastSender<String>> {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0)
.into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for FrbWrapper<BroadcastSender<String>>
{
}
impl flutter_rust_bridge::IntoIntoDart<FrbWrapper<BroadcastSender<String>>>
for BroadcastSender<String>
{
fn into_into_dart(self) -> FrbWrapper<BroadcastSender<String>> {
self.into()
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for FrbWrapper<OpaqueSender> { impl flutter_rust_bridge::IntoDart for FrbWrapper<OpaqueSender> {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
@ -7134,18 +6898,6 @@ impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error {
} }
} }
impl SseEncode for BroadcastSender<String> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<RustOpaqueMoi<
flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BroadcastSender<String>>,
>>::sse_encode(
flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self),
serializer,
);
}
}
impl SseEncode for OpaqueSender { impl SseEncode for OpaqueSender {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@ -7179,26 +6931,6 @@ impl SseEncode for SpotubePlugin {
} }
} }
impl SseEncode for flutter_rust_bridge::DartOpaque {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<usize>::sse_encode(self.encode(), serializer);
}
}
impl SseEncode
for RustOpaqueMoi<
flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BroadcastSender<String>>,
>
{
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
let (ptr, size) = self.sse_encode_raw();
<usize>::sse_encode(ptr, serializer);
<i32>::sse_encode(size, serializer);
}
}
impl SseEncode impl SseEncode
for RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<OpaqueSender>> for RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<OpaqueSender>>
{ {
@ -7316,16 +7048,6 @@ impl SseEncode for i64 {
} }
} }
impl SseEncode for isize {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
serializer
.cursor
.write_i64::<NativeEndian>(self as _)
.unwrap();
}
}
impl SseEncode for Vec<String> { impl SseEncode for Vec<String> {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@ -8164,7 +7886,6 @@ mod io {
// Section: imports // Section: imports
use super::*; use super::*;
use crate::api::host_api::webview::*;
use crate::api::plugin::commands::*; use crate::api::plugin::commands::*;
use crate::api::plugin::plugin::*; use crate::api::plugin::plugin::*;
use crate::*; use crate::*;
@ -8180,20 +7901,6 @@ mod io {
flutter_rust_bridge::frb_generated_boilerplate_io!(); flutter_rust_bridge::frb_generated_boilerplate_io!();
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_spotube_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
ptr: *const std::ffi::c_void,
) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BroadcastSender < String >>>::increment_strong_count(ptr as _);
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_spotube_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBroadcastSenderString(
ptr: *const std::ffi::c_void,
) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BroadcastSender < String >>>::decrement_strong_count(ptr as _);
}
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_spotube_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender( pub extern "C" fn frbgen_spotube_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOpaqueSender(
ptr: *const std::ffi::c_void, ptr: *const std::ffi::c_void,

View File

@ -0,0 +1,167 @@
use eventsource_client::{ClientBuilder, Client, SSE};
use flutter_rust_bridge::for_generated::futures::StreamExt;
use rquickjs::function::Func;
use rquickjs::{CatchResultExt, Ctx, Error as JsError, Function, Object, Value};
use tokio::sync::mpsc;
fn connect_sse<'js>(ctx: Ctx<'js>, config: Object<'js>) -> rquickjs::Result<Object<'js>> {
let url: String = config.get("url")?;
let on_connecting: Function = config.get("onConnecting")?;
let on_open: Function = config.get("onOpen")?;
let on_message: Function = config.get("onMessage")?;
let on_error: Function = config.get("onError")?;
let (close_tx, mut close_rx) = mpsc::unbounded_channel::<()>();
if let Err(e) = on_connecting.call::<_, ()>(()).catch(&ctx) {
eprintln!("Error in onConnecting callback: {}", e);
}
// Spawn the SSE background task using Ctx::spawn
let _ = ctx.clone().spawn(async move {
let client = ClientBuilder::for_url(&url);
if let Err(err) = client {
eprintln!("Error in ClientBuilder::for_url: {}", err);
return;
}
let client = client.unwrap().build();
// Notify "open"
if let Err(e) = on_open.call::<(), ()>(()) {
eprintln!("Error in onOpen callback: {}", e);
}
// Now listen to SSE events OR close signal
let mut stream = Box::pin(client.stream());
loop {
tokio::select! {
// Check for close signal first
_ = close_rx.recv() => {
// Close requested — drop stream and exit
drop(stream);
break;
}
event = stream.next() => {
match event {
Some(Ok(SSE::Event(msg))) => {
let data = msg.data.clone();
if let Err(e) = on_message.call::<_, ()>((data,)) {
eprintln!("Error in onMessage callback: {}", e);
}
}
Some(Ok(SSE::Connected(details))) => {
println!("SSE Connected: {:?}", details);
}
Some(Ok(SSE::Comment(comment))) => {
println!("SSE Comment: {}", comment);
}
Some(Err(err)) => {
if let Err(e) = on_error.call::<_, ()>((err.to_string(),)) {
eprintln!("Error in onError callback: {}", e);
}
break;
}
None => {
println!("SSE Stream ended gracefully");
break;
}
}
}
}
}
});
// Create the close function that sends signal via channel
let close_fn = Function::new(ctx.clone(), move |_ctx: Ctx<'_>| {
// Send close signal — ignore errors if receiver is gone
let _ = close_tx.send(());
Ok::<(), JsError>(())
})?;
// Return { close: () => void }
let result = Object::new(ctx)?;
result.set("close", close_fn)?;
Ok(result)
}
pub fn init(ctx: &Ctx) -> rquickjs::Result<()> {
let globals = ctx.globals();
globals.set("__connectSSE", Func::new(connect_sse))?;
ctx.eval::<Value, _>(
r#"
globalThis.EventSource = class EventSource {
#listeners = {};
constructor(url, options) {
this.url = url;
this.options = options;
this.close = __connectSSE({
url: this.url,
onConnecting: this.#onConnecting.bind(this),
onOpen: this.#onOpen.bind(this),
onMessage: this.#onMessage.bind(this),
onError: this.#onError.bind(this),
}).close;
}
#onMessage(data) {
console.log("Received message:", data);
if (this.onmessage) {
this.onmessage(data);
}
const eventLines = data.split('\n');
if(eventLines.length === 0) return;
const eventNameChunks = eventLines[0].split("event:");
if(eventNameChunks.length === 0) return;
const eventName = eventNameChunks[1].trim();
if (!this.#listeners[eventName]) return;
const eventDataChunks = eventLines[1].split("data:");
if(eventDataChunks.length === 0) return;
const eventData = eventDataChunks[1].trim();
if (!eventData) return;
this.#listeners[eventName](eventData);
}
#onConnecting() {
this.readyState = 0;
}
#onOpen() {
this.readyState = 1;
if (this.onopen) {
this.onopen();
}
}
#onError(error) {
this.readyState = 2;
if (this.onerror) {
this.onerror(error);
}
}
addEventListener(event, callback) {
this.#listeners[event] ??= [];
this.#listeners[event].push(callback);
}
}
"#,
)?;
Ok(())
}

View File

@ -1 +1,2 @@
pub mod fetcher; pub mod event_source;
pub mod webview;

View File

@ -0,0 +1,215 @@
use eventsource_client::{Client as EventSourceClient, ClientBuilder};
use flutter_rust_bridge::for_generated::futures::StreamExt;
use rquickjs::{class::Trace, Class, Ctx, Function, JsLifetime};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize)]
struct WebViewURLRequest {
url: String,
}
#[derive(Serialize, Deserialize)]
struct WebViewUIDRequest {
uid: String,
}
#[derive(Serialize, Deserialize)]
struct WebViewCookiesRequest {
url: String,
uid: String,
}
#[derive(Serialize, Deserialize)]
struct WebViewResponse {
uid: String,
}
#[derive(Clone, Trace, JsLifetime)]
#[rquickjs::class]
pub struct WebView<'js> {
pub url: String,
pub uid: String,
#[qjs(skip_trace)]
endpoint_url: String,
#[qjs(skip_trace)]
secret: String,
#[qjs(skip_trace)]
callbacks: Vec<Function<'js>>,
}
#[rquickjs::methods]
impl<'js> WebView<'js> {
#[qjs(constructor)]
pub fn new(url: String, uid: String, endpoint_url: String, secret: String) -> Self {
Self {
url,
uid,
endpoint_url,
secret,
callbacks: Vec::new(),
}
}
#[qjs(static)]
pub async fn create(ctx: Ctx<'js>, url: String) -> rquickjs::Result<Class<'js, WebView<'js>>> {
let endpoint_url: String = ctx.globals().get("__webviewUrl").unwrap_or_default();
let secret: String = ctx.globals().get("__webviewSecret").unwrap_or_default();
let client = reqwest::Client::new();
let endpoint = format!("{}/plugin-api/webview/create", endpoint_url.clone());
let response = client
.post(&endpoint)
.header("Content-Type", "application/json")
.header("X-Plugin-Secret", &secret)
.json(&WebViewURLRequest { url: url.clone() })
.send()
.await
.map_err(|e| {
rquickjs::Error::new_from_js_message("reqwest", "Error", &e.to_string())
})?;
let data: WebViewResponse = response.json().await.map_err(|e| {
rquickjs::Error::new_from_js_message("reqwest", "Error", &e.to_string())
})?;
let webview = WebView::new(url, data.uid, endpoint_url, secret);
Class::instance(ctx, webview)
}
pub async fn open(&self) -> rquickjs::Result<()> {
let client = reqwest::Client::new();
let endpoint = format!("{}/plugin-api/webview/open", self.endpoint_url);
client
.post(&endpoint)
.header("Content-Type", "application/json")
.header("X-Plugin-Secret", &self.secret)
.json(&WebViewUIDRequest {
uid: self.uid.clone(),
})
.send()
.await
.map_err(|e| {
rquickjs::Error::new_from_js_message("reqwest", "Error", &e.to_string())
})?;
self.url_change_task().await;
Ok(())
}
pub async fn cookies(&self, ctx: Ctx<'js>) -> rquickjs::Result<rquickjs::Value<'js>> {
let client = reqwest::Client::new();
let endpoint = format!("{}/plugin-api/webview/cookies", self.endpoint_url);
let response = client
.post(&endpoint)
.header("Content-Type", "application/json")
.header("X-Plugin-Secret", &self.secret)
.json(&WebViewCookiesRequest {
url: self.url.clone(),
uid: self.uid.clone(),
})
.send()
.await
.map_err(|e| {
rquickjs::Error::new_from_js_message("reqwest", "Error", &e.to_string())
})?;
let data: serde_json::Value = response.json().await.map_err(|e| {
rquickjs::Error::new_from_js_message("reqwest", "Error", &e.to_string())
})?;
let value = ctx.json_parse(data.to_string())?;
Ok(value)
}
pub async fn close(&self) -> rquickjs::Result<()> {
let client = reqwest::Client::new();
let endpoint = format!("{}/plugin-api/webview/close", self.endpoint_url);
client
.post(&endpoint)
.header("Content-Type", "application/json")
.header("X-Plugin-Secret", &self.secret)
.json(&WebViewUIDRequest {
uid: self.uid.clone(),
})
.send()
.await
.map_err(|e| {
rquickjs::Error::new_from_js_message("reqwest", "Error", &e.to_string())
})?;
Ok(())
}
#[qjs(rename = "onUrlChange")]
pub fn on_url_change(&mut self, callback: Function<'js>) -> rquickjs::Result<()> {
self.callbacks.push(callback);
Ok(())
}
async fn url_change_task(&self) {
let endpoint = format!(
"{}/plugin-api/webview/{}/on-url-request",
self.endpoint_url, self.uid
);
let secret = self.secret.clone();
let mut backoff = 1u64;
const MAX_BACKOFF: u64 = 60;
loop {
let client = ClientBuilder::for_url(&endpoint)
.expect("Failed to create EventSourceClient")
.header("X-Plugin-Secret", &secret)
.expect("Failed to set header for EventSourceClient")
.build();
let mut stream = client.stream();
while let Some(event) = stream.next().await {
match event {
Ok(eventsource_client::SSE::Event(msg)) => {
if msg.event_type != "url-request" {
continue;
}
backoff = 1;
if let Ok(data) = serde_json::from_str::<HashMap<String, String>>(&msg.data)
{
let url = data.get("url").cloned().unwrap_or_default();
for callback in self.callbacks.iter() {
if let Err(e) = callback.call::<_, ()>((url.clone(),)) {
eprintln!("Error in onUrlChange callback: {}", e);
}
}
} else {
eprintln!("Failed to parse event data: {}", msg.data);
}
}
Ok(_) => {}
Err(err) => {
eprintln!("Error in EventSource stream: {}", err);
}
}
}
eprintln!("EventSource disconnected. Reconnecting in {}s...", backoff);
tokio::time::sleep(tokio::time::Duration::from_secs(backoff)).await;
backoff = (backoff * 2).min(MAX_BACKOFF);
}
}
}
pub fn init(ctx: &Ctx, endpoint_url: String, secret: String) -> rquickjs::Result<()> {
// Store config in globals for access in static methods
ctx.globals().set("__webviewUrl", endpoint_url)?;
ctx.globals().set("__webviewSecret", secret)?;
// Register the WebView class
Class::<WebView>::define(&ctx.globals())?;
Ok(())
}

View File

@ -1,5 +1,4 @@
pub mod album; pub mod album;
pub mod apis;
pub mod artist; pub mod artist;
pub mod audio_source; pub mod audio_source;
pub mod browse; pub mod browse;
@ -10,4 +9,5 @@ pub mod track;
pub mod user; pub mod user;
pub mod auth; pub mod auth;
pub(crate) mod utils; pub(crate) mod utils;
pub(crate) mod apis;
// Export Context // Export Context

View File

@ -1,6 +1,6 @@
mod api; mod api;
mod internal;
mod frb_generated; mod frb_generated;
mod internal;
use rquickjs::function::{Async, Func}; use rquickjs::function::{Async, Func};
use rquickjs::{async_with, AsyncContext, AsyncRuntime, Function, Object, Promise}; use rquickjs::{async_with, AsyncContext, AsyncRuntime, Function, Object, Promise};
@ -100,7 +100,7 @@ async fn plugin() -> anyhow::Result<()> {
repository: None, repository: None,
version: "0.1.0".to_string(), version: "0.1.0".to_string(),
}; };
let sender = plugin.create_context(PLUGIN_JS.to_string(), config.clone())?; let sender = plugin.create_context(PLUGIN_JS.to_string(), config.clone(), "".to_string(), "".to_string())?;
let (r1, r2) = tokio::join!( let (r1, r2) = tokio::join!(
plugin.core.check_update(&sender, config.clone()), plugin.core.check_update(&sender, config.clone()),
plugin.core.check_update(&sender, config.clone()) plugin.core.check_update(&sender, config.clone())