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