mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-08 16:27:31 +00:00
feat: add localStorage API support
This commit is contained in:
parent
6da7fb7ac3
commit
949519aa61
@ -210,19 +210,21 @@ class CoreEndpoint {
|
||||
// }
|
||||
// });
|
||||
// await webview.open();
|
||||
const res = await SpotubeForm.show("Hello", [
|
||||
{
|
||||
objectType: "input",
|
||||
id: "email",
|
||||
variant: "text",
|
||||
placeholder: "Enter your email",
|
||||
defaultValue: null,
|
||||
required: true,
|
||||
regex: null,
|
||||
}
|
||||
])
|
||||
// const res = await SpotubeForm.show("Hello", [
|
||||
// {
|
||||
// objectType: "input",
|
||||
// id: "email",
|
||||
// variant: "text",
|
||||
// placeholder: "Enter your email",
|
||||
// defaultValue: null,
|
||||
// required: true,
|
||||
// regex: null,
|
||||
// }
|
||||
// ])
|
||||
// console.log("Form Result: ", res);
|
||||
|
||||
console.log("Form Result: ", res);
|
||||
console.log("LocalStorage Value: ", localStorage.getItem("test_key"));
|
||||
localStorage.setItem("test_key", "test_value");
|
||||
}
|
||||
}
|
||||
class Plugin {
|
||||
|
||||
@ -4,6 +4,7 @@ import 'package:shelf_router/shelf_router.dart';
|
||||
import 'package:spotube/provider/server/routes/connect.dart';
|
||||
import 'package:spotube/provider/server/routes/playback.dart';
|
||||
import 'package:spotube/provider/server/routes/plugin_apis/form.dart';
|
||||
import 'package:spotube/provider/server/routes/plugin_apis/path_provider.dart';
|
||||
import 'package:spotube/provider/server/routes/plugin_apis/webview.dart';
|
||||
|
||||
Handler pluginApiAuthMiddleware(Handler handler) {
|
||||
@ -58,6 +59,10 @@ final serverRouterProvider = Provider((ref) {
|
||||
"/plugin-api/form/show",
|
||||
pluginApiAuthMiddleware(formRoutes.showForm),
|
||||
);
|
||||
router.get(
|
||||
"/plugin/localstorage/directories",
|
||||
pluginApiAuthMiddleware(ServerPathProviderRoutes.getDirectories),
|
||||
);
|
||||
|
||||
router.all("/ws", connectRoutes.websocket);
|
||||
|
||||
|
||||
29
lib/provider/server/routes/plugin_apis/path_provider.dart
Normal file
29
lib/provider/server/routes/plugin_apis/path_provider.dart
Normal file
@ -0,0 +1,29 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path_provider/path_provider.dart' as pp;
|
||||
import 'package:shelf/shelf.dart';
|
||||
|
||||
class ServerPathProviderRoutes {
|
||||
static Future<Response> getDirectories(Request request) async {
|
||||
final directories = <String, Directory?>{
|
||||
'temporary': await Future<Directory?>.value(pp.getTemporaryDirectory())
|
||||
.catchError((e) => null),
|
||||
'applicationDocuments':
|
||||
await Future<Directory?>.value(pp.getApplicationDocumentsDirectory())
|
||||
.catchError((e) => null),
|
||||
'applicationSupport':
|
||||
await Future<Directory?>.value(pp.getApplicationSupportDirectory())
|
||||
.catchError((e) => null),
|
||||
'library': await Future<Directory?>.value(pp.getLibraryDirectory())
|
||||
.catchError((e) => null),
|
||||
'externalStorage':
|
||||
await pp.getExternalStorageDirectory().catchError((e) => null),
|
||||
'downloads': await pp.getDownloadsDirectory().catchError((e) => null),
|
||||
}.map((key, value) => MapEntry(key, value?.path));
|
||||
return Response.ok(
|
||||
jsonEncode(directories),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
}
|
||||
}
|
||||
88
rust/Cargo.lock
generated
88
rust/Cargo.lock
generated
@ -404,6 +404,19 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "confy"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8807c397789cbe02bbdb1a27ea5f345584132808697b2a3f957c829829ee4814"
|
||||
dependencies = [
|
||||
"etcetera",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
@ -721,6 +734,17 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "etcetera"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"home",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.0"
|
||||
@ -1558,9 +1582,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
@ -2898,6 +2922,7 @@ name = "rust_lib_spotube"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"confy",
|
||||
"eventsource-client",
|
||||
"flutter_rust_bridge",
|
||||
"heck",
|
||||
@ -3163,6 +3188,15 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@ -3373,6 +3407,26 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threadpool"
|
||||
version = "1.8.1"
|
||||
@ -3474,6 +3528,21 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.3"
|
||||
@ -3504,6 +3573,12 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
@ -3884,6 +3959,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
|
||||
@ -21,6 +21,7 @@ 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"] }
|
||||
eventsource-client = "0.15.1"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
confy = "2.0.0"
|
||||
|
||||
[patch."https://github.com/DelSkayn/rquickjs"]
|
||||
rquickjs = "0.10.0"
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
use std::fmt::format;
|
||||
use crate::api::plugin::commands::PluginCommand;
|
||||
use crate::api::plugin::executors::{
|
||||
execute_albums, execute_artists, execute_audio_source, execute_auth, execute_browse,
|
||||
@ -13,7 +12,7 @@ use crate::api::plugin::senders::{
|
||||
};
|
||||
use crate::frb_generated::StreamSink;
|
||||
use crate::internal::apis;
|
||||
use crate::internal::apis::{form, webview};
|
||||
use crate::internal::apis::{form, get_platform_directories, webview};
|
||||
use anyhow::anyhow;
|
||||
use flutter_rust_bridge::{frb, Rust2DartSendError};
|
||||
use llrt_modules::module_builder::ModuleBuilder;
|
||||
@ -37,6 +36,7 @@ pub struct OpaqueSender {
|
||||
async fn create_context(
|
||||
server_endpoint_url: String,
|
||||
server_secret: String,
|
||||
plugin_slug: String,
|
||||
) -> anyhow::Result<(AsyncContext, AsyncRuntime)> {
|
||||
let runtime = AsyncRuntime::new().expect("Unable to create async runtime");
|
||||
|
||||
@ -66,13 +66,19 @@ async fn create_context(
|
||||
.await
|
||||
.expect("Unable to create async context");
|
||||
|
||||
let directories =
|
||||
get_platform_directories(server_endpoint_url.clone(), server_secret.clone()).await?;
|
||||
let local_storage_dir = directories
|
||||
.application_support
|
||||
.ok_or_else(|| anyhow!("Application support directory not found"))?;
|
||||
|
||||
async_with!(context => |ctx| {
|
||||
apis::init(&ctx, server_endpoint_url, server_secret)?;
|
||||
apis::init(&ctx, server_endpoint_url, server_secret).catch(&ctx).map_err(|e| anyhow!("Failed to initialize APIs: {}", e))?;
|
||||
apis::local_storage::init(&ctx, plugin_slug, local_storage_dir).catch(&ctx).map_err(|e| anyhow!("Failed to initialize LocalStorage API: {}", e))?;
|
||||
global_attachment.attach(&ctx).catch(&ctx).map_err(|e| anyhow!("Failed to attach global modules: {}", e))?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to register globals: {}", e))?;
|
||||
.await?;
|
||||
|
||||
Ok((context, runtime))
|
||||
}
|
||||
@ -159,7 +165,7 @@ impl SpotubePlugin {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[frb(sync)]
|
||||
#[frb(sync)]
|
||||
pub fn create_context(
|
||||
&self,
|
||||
plugin_script: String,
|
||||
@ -179,6 +185,7 @@ impl SpotubePlugin {
|
||||
let (ctx, _) = create_context(
|
||||
server_endpoint_url,
|
||||
server_secret,
|
||||
plugin_config.slug(),
|
||||
).await?;
|
||||
|
||||
let injection = format!(
|
||||
|
||||
@ -1,167 +0,0 @@
|
||||
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(())
|
||||
}
|
||||
115
rust/src/internal/apis/local_storage.rs
Normal file
115
rust/src/internal/apis/local_storage.rs
Normal file
@ -0,0 +1,115 @@
|
||||
use rquickjs::class::Trace;
|
||||
use rquickjs::{Class, Ctx, JsLifetime, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// All values stored as strings; we convert at the edges.
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
struct LocalStorageConfig {
|
||||
map: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// LocalStorage backed by `confy`.
|
||||
#[derive(Clone, JsLifetime, Trace)]
|
||||
#[rquickjs::class]
|
||||
pub struct LocalStorage {
|
||||
#[qjs(skip_trace)]
|
||||
prefix: String,
|
||||
#[qjs(skip_trace)]
|
||||
path: String,
|
||||
#[qjs(skip_trace)]
|
||||
state: Arc<Mutex<LocalStorageConfig>>,
|
||||
}
|
||||
|
||||
fn merge_prefix(prefix: String, key: String) -> String {
|
||||
format!("{}->{}", prefix, key)
|
||||
}
|
||||
|
||||
#[rquickjs::methods]
|
||||
impl LocalStorage {
|
||||
#[qjs(constructor)]
|
||||
pub fn new(prefix: String, directory: String) -> rquickjs::Result<Self> {
|
||||
let path = Path::new(&directory).join("plugin_configs.toml");
|
||||
let cfg: LocalStorageConfig = confy::load_path(path.clone()).map_err(|e| {
|
||||
rquickjs::Error::new_from_js_message(
|
||||
"local_storage",
|
||||
"PersistenceError",
|
||||
&e.to_string(),
|
||||
)
|
||||
})?;
|
||||
Ok(Self {
|
||||
prefix,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
state: Arc::new(Mutex::new(cfg)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Persist current state to disk.
|
||||
fn persist(&self) -> rquickjs::Result<()> {
|
||||
let cfg = self.state.lock().unwrap().clone();
|
||||
confy::store_path(self.path.clone(), cfg).map_err(|e| {
|
||||
rquickjs::Error::new_from_js_message(
|
||||
"local_storage",
|
||||
"PersistenceError",
|
||||
&e.to_string(),
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[qjs(rename = "setItem")]
|
||||
pub fn set_item(&self, key: String, value: String) -> rquickjs::Result<()> {
|
||||
{
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state
|
||||
.map
|
||||
.insert(merge_prefix(self.prefix.clone(), key), value);
|
||||
}
|
||||
self.persist()
|
||||
}
|
||||
|
||||
#[qjs(rename = "getItem")]
|
||||
pub fn get_item(&self, key: String) -> rquickjs::Result<Option<String>> {
|
||||
let state = self.state.lock().map_err(|e| {
|
||||
rquickjs::Error::new_from_js_message("local_storage", "LockError", &e.to_string())
|
||||
})?;
|
||||
let key = merge_prefix(self.prefix.clone(), key.clone());
|
||||
Ok(state.map.get(key.as_str()).cloned())
|
||||
}
|
||||
|
||||
#[qjs(rename = "removeItem")]
|
||||
pub fn remove_item(&self, key: String) -> rquickjs::Result<()> {
|
||||
{
|
||||
let mut state = self.state.lock().map_err(|e| {
|
||||
rquickjs::Error::new_from_js_message("local_storage", "LockError", &e.to_string())
|
||||
})?;
|
||||
state
|
||||
.map
|
||||
.remove(merge_prefix(self.prefix.clone(), key).as_str());
|
||||
}
|
||||
self.persist()
|
||||
}
|
||||
|
||||
pub fn clear(&self) -> rquickjs::Result<()> {
|
||||
{
|
||||
let mut state = self.state.lock().map_err(|e| {
|
||||
rquickjs::Error::new_from_js_message("local_storage", "LockError", &e.to_string())
|
||||
})?;
|
||||
state.map.clear();
|
||||
}
|
||||
self.persist()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(ctx: &Ctx, prefix: String, directory: String) -> rquickjs::Result<()> {
|
||||
let global = ctx.globals();
|
||||
Class::<LocalStorage>::define(&global)?;
|
||||
|
||||
ctx.eval::<Value, _>(format!(
|
||||
"globalThis.localStorage = new LocalStorage('{}', '{}');",
|
||||
prefix, directory
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
use rquickjs::Ctx;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod event_source;
|
||||
pub mod form;
|
||||
pub mod local_storage;
|
||||
pub mod webview;
|
||||
|
||||
pub fn init(ctx: &Ctx, endpoint_url: String, secret: String) -> rquickjs::Result<()> {
|
||||
@ -9,3 +10,28 @@ pub fn init(ctx: &Ctx, endpoint_url: String, secret: String) -> rquickjs::Result
|
||||
ctx.globals().set("__serverSecret", secret)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DirectoriesResponse {
|
||||
pub temporary: Option<String>,
|
||||
pub application_documents: Option<String>,
|
||||
pub application_support: Option<String>,
|
||||
pub library: Option<String>,
|
||||
pub external_storage: Option<String>,
|
||||
pub downloads: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_platform_directories(
|
||||
server_url: String,
|
||||
server_secret: String,
|
||||
) -> anyhow::Result<DirectoriesResponse> {
|
||||
let client = reqwest::Client::new();
|
||||
Ok(client
|
||||
.get(format!("{}/plugin/localstorage/directories", server_url).as_str())
|
||||
.header("X-Plugin-Secret", server_secret.as_str())
|
||||
.send()
|
||||
.await?
|
||||
.json::<DirectoriesResponse>()
|
||||
.await?)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user