tinymist_package/registry/
browser.rs

1//! Browser proxy registry for tinymist. You should implement interfaces in js.
2
3use std::{io::Read, path::Path};
4
5use js_sys::Uint8Array;
6use typst::diag::{EcoString, eco_format};
7use wasm_bindgen::{JsValue, prelude::*};
8
9use super::{PackageError, PackageRegistry, PackageSpec};
10
11/// The `ProxyContext` struct is a wrapper around a JavaScript this.
12#[wasm_bindgen]
13#[derive(Debug, Clone)]
14pub struct ProxyContext {
15    context: JsValue,
16}
17
18#[wasm_bindgen]
19impl ProxyContext {
20    /// Creates a new `ProxyContext` instance.
21    #[wasm_bindgen(constructor)]
22    pub fn new(context: JsValue) -> Self {
23        Self { context }
24    }
25
26    /// Returns the JavaScript this.
27    #[wasm_bindgen(getter)]
28    pub fn context(&self) -> JsValue {
29        self.context.clone()
30    }
31
32    /// A convenience function to untar a tarball and call a callback for each
33    /// entry.
34    pub fn untar(&self, data: &[u8], cb: js_sys::Function) -> Result<(), JsValue> {
35        let cb = move |key: String, value: &[u8], mtime: u64| -> Result<(), JsValue> {
36            let key = JsValue::from_str(&key);
37            let value = Uint8Array::from(value);
38            let mtime = JsValue::from_f64(mtime as f64);
39            cb.call3(&self.context, &key, &value, &mtime).map(|_| ())
40        };
41
42        let decompressed = flate2::read::GzDecoder::new(data);
43        let mut reader = tar::Archive::new(decompressed);
44        let entries = reader.entries();
45        let entries = entries.map_err(|err| {
46            let t = PackageError::MalformedArchive(Some(eco_format!("{err}")));
47            JsValue::from_str(&format!("{t:?}"))
48        })?;
49
50        let mut buf = Vec::with_capacity(1024);
51        for entry in entries {
52            // Read single entry
53            let mut entry = entry.map_err(|e| format!("{e:?}"))?;
54            let header = entry.header();
55
56            let is_file = header.entry_type().is_file();
57            if !is_file {
58                continue;
59            }
60
61            let mtime = header.mtime().unwrap_or(0);
62
63            let path = header.path().map_err(|e| format!("{e:?}"))?;
64            let path = path.to_string_lossy().as_ref().to_owned();
65
66            let size = header.size().map_err(|e| format!("{e:?}"))?;
67            buf.clear();
68            buf.reserve(size as usize);
69            entry.read_to_end(&mut buf).map_err(|e| format!("{e:?}"))?;
70
71            cb(path, &buf, mtime)?
72        }
73
74        Ok(())
75    }
76}
77
78/// The `JsRegistry` struct is a wrapper around a JavaScript function that
79#[derive(Debug)]
80pub struct JsRegistry {
81    /// The JavaScript this context.
82    pub context: ProxyContext,
83    /// The JavaScript function to call for resolving packages.
84    pub real_resolve_fn: js_sys::Function,
85}
86
87impl PackageRegistry for JsRegistry {
88    fn resolve(&self, spec: &PackageSpec) -> Result<std::sync::Arc<Path>, PackageError> {
89        // prepare js_spec
90        let js_spec = js_sys::Object::new();
91        js_sys::Reflect::set(&js_spec, &"name".into(), &spec.name.to_string().into()).unwrap();
92        js_sys::Reflect::set(
93            &js_spec,
94            &"namespace".into(),
95            &spec.namespace.to_string().into(),
96        )
97        .unwrap();
98        js_sys::Reflect::set(
99            &js_spec,
100            &"version".into(),
101            &spec.version.to_string().into(),
102        )
103        .unwrap();
104
105        self.real_resolve_fn
106            .call1(&self.context.clone().into(), &js_spec)
107            .map_err(|e| PackageError::Other(Some(eco_format!("{:?}", e))))
108            .and_then(|v| {
109                if v.is_undefined() {
110                    Err(PackageError::NotFound(spec.clone()))
111                } else {
112                    Ok(Path::new(&v.as_string().unwrap()).into())
113                }
114            })
115    }
116
117    // todo: provide package list for browser
118    fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
119        &[]
120    }
121}
122
123// todo
124/// Safety: `JsRegistry` is only used in the browser environment, and we cannot
125/// share data between workers.
126unsafe impl Send for JsRegistry {}
127/// Safety: `JsRegistry` is only used in the browser environment, and we cannot
128/// share data between workers.
129unsafe impl Sync for JsRegistry {}