mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-24 13:10:07 +00:00
Some checks failed
tinymist::auto_tag / auto-tag (push) Has been cancelled
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / announce (push) Has been cancelled
tinymist::ci / build (push) Has been cancelled
Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>
143 lines
4.5 KiB
Rust
143 lines
4.5 KiB
Rust
//! Browser proxy registry for tinymist. You should implement interfaces in js.
|
|
|
|
use std::{io::Read, path::Path};
|
|
|
|
use js_sys::Uint8Array;
|
|
use tinymist_std::ImmutPath;
|
|
use typst::diag::{EcoString, eco_format};
|
|
use wasm_bindgen::{JsValue, prelude::*};
|
|
|
|
use super::{PackageError, PackageRegistry, PackageSpec};
|
|
|
|
/// The `ProxyContext` struct is a wrapper around a JavaScript this.
|
|
#[wasm_bindgen]
|
|
#[derive(Debug, Clone)]
|
|
pub struct ProxyContext {
|
|
context: JsValue,
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
impl ProxyContext {
|
|
/// Creates a new `ProxyContext` instance.
|
|
#[wasm_bindgen(constructor)]
|
|
pub fn new(context: JsValue) -> Self {
|
|
Self { context }
|
|
}
|
|
|
|
/// Returns the JavaScript this.
|
|
#[wasm_bindgen(getter)]
|
|
pub fn context(&self) -> JsValue {
|
|
self.context.clone()
|
|
}
|
|
|
|
/// A convenience function to untar a tarball and call a callback for each
|
|
/// entry.
|
|
pub fn untar(&self, data: &[u8], cb: js_sys::Function) -> Result<(), JsValue> {
|
|
let cb = move |key: String, value: &[u8], mtime: u64| -> Result<(), JsValue> {
|
|
let key = JsValue::from_str(&key);
|
|
let value = Uint8Array::from(value);
|
|
let mtime = JsValue::from_f64(mtime as f64);
|
|
cb.call3(&self.context, &key, &value, &mtime).map(|_| ())
|
|
};
|
|
|
|
let decompressed = flate2::read::GzDecoder::new(data);
|
|
let mut reader = tar::Archive::new(decompressed);
|
|
let entries = reader.entries();
|
|
let entries = entries.map_err(|err| {
|
|
let t = PackageError::MalformedArchive(Some(eco_format!("{err}")));
|
|
JsValue::from_str(&format!("{t:?}"))
|
|
})?;
|
|
|
|
let mut buf = Vec::with_capacity(1024);
|
|
for entry in entries {
|
|
// Read single entry
|
|
let mut entry = entry.map_err(|e| format!("{e:?}"))?;
|
|
let header = entry.header();
|
|
|
|
let is_file = header.entry_type().is_file();
|
|
if !is_file {
|
|
continue;
|
|
}
|
|
|
|
let mtime = header.mtime().unwrap_or(0);
|
|
|
|
let path = header.path().map_err(|e| format!("{e:?}"))?;
|
|
let path = path.to_string_lossy().as_ref().to_owned();
|
|
|
|
let size = header.size().map_err(|e| format!("{e:?}"))?;
|
|
buf.clear();
|
|
buf.reserve(size as usize);
|
|
entry.read_to_end(&mut buf).map_err(|e| format!("{e:?}"))?;
|
|
|
|
cb(path, &buf, mtime)?
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// The `JsRegistry` struct is a wrapper around a JavaScript function that
|
|
#[derive(Debug)]
|
|
pub struct JsRegistry {
|
|
/// The JavaScript this context.
|
|
pub context: ProxyContext,
|
|
/// The JavaScript function to call for resolving packages.
|
|
pub real_resolve_fn: js_sys::Function,
|
|
}
|
|
|
|
impl PackageRegistry for JsRegistry {
|
|
fn resolve(&self, spec: &PackageSpec) -> Result<std::sync::Arc<Path>, PackageError> {
|
|
// prepare js_spec
|
|
let js_spec = js_sys::Object::new();
|
|
js_sys::Reflect::set(&js_spec, &"name".into(), &spec.name.to_string().into()).unwrap();
|
|
js_sys::Reflect::set(
|
|
&js_spec,
|
|
&"namespace".into(),
|
|
&spec.namespace.to_string().into(),
|
|
)
|
|
.unwrap();
|
|
js_sys::Reflect::set(
|
|
&js_spec,
|
|
&"version".into(),
|
|
&spec.version.to_string().into(),
|
|
)
|
|
.unwrap();
|
|
|
|
self.real_resolve_fn
|
|
.call1(&self.context.clone().into(), &js_spec)
|
|
.map_err(|e| PackageError::Other(Some(eco_format!("{:?}", e))))
|
|
.and_then(|v| {
|
|
if v.is_undefined() {
|
|
Err(PackageError::NotFound(spec.clone()))
|
|
} else {
|
|
Ok(Path::new(&v.as_string().unwrap()).into())
|
|
}
|
|
})
|
|
}
|
|
|
|
// todo: provide package list for browser
|
|
fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
|
|
&[]
|
|
}
|
|
}
|
|
|
|
impl JsRegistry {
|
|
/// Returns the path at which non-local packages should be stored when
|
|
/// downloaded.
|
|
pub fn package_cache_path(&self) -> Option<&ImmutPath> {
|
|
None
|
|
}
|
|
|
|
/// Returns the path at which local packages are stored.
|
|
pub fn package_path(&self) -> Option<&ImmutPath> {
|
|
None
|
|
}
|
|
}
|
|
|
|
// todo
|
|
/// Safety: `JsRegistry` is only used in the browser environment, and we cannot
|
|
/// share data between workers.
|
|
unsafe impl Send for JsRegistry {}
|
|
/// Safety: `JsRegistry` is only used in the browser environment, and we cannot
|
|
/// share data between workers.
|
|
unsafe impl Sync for JsRegistry {}
|