tinymist/crates/tinymist-package/src/registry/browser.rs
ParaN3xus b239224a63
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
feat: add js package registry support for tinymist-wasm (#2102)
Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>
2025-10-01 16:40:21 +08:00

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 {}