Skip to main content

nightshade_api/
transfer.rs

1//! Envelope encoding shared by the page and the worker: every
2//! `postMessage` is a plain object whose fields are the
3//! [`crate::wire::MESSAGE_KEY`] control message or the
4//! [`crate::wire::APP_KEY`] application message, plus optional
5//! [`crate::wire::ATTACHMENTS_KEY`] binary payloads whose buffers ride the
6//! transfer list.
7
8use crate::wire::{APP_KEY, ATTACHMENTS_KEY, Attachments, MESSAGE_KEY};
9use serde::Serialize;
10use wasm_bindgen::{JsCast, JsValue};
11
12/// Builds an envelope carrying `message` under `key`, or `None` when the
13/// message does not serialize.
14pub fn envelope_with<Message: Serialize>(key: &str, message: &Message) -> Option<js_sys::Object> {
15    let value = serde_wasm_bindgen::to_value(message).ok()?;
16    let envelope = js_sys::Object::new();
17    js_sys::Reflect::set(&envelope, &JsValue::from_str(key), &value).ok()?;
18    Some(envelope)
19}
20
21/// Adds named binary payloads to `envelope` and their buffers to
22/// `transfer`. Returns false when the attachment bag could not be written.
23pub fn attach(
24    envelope: &js_sys::Object,
25    transfer: &js_sys::Array,
26    attachments: &[(String, Vec<u8>)],
27) -> bool {
28    let bag = js_sys::Object::new();
29    for (name, bytes) in attachments {
30        let array = js_sys::Uint8Array::new_with_length(bytes.len() as u32);
31        array.copy_from(bytes);
32        if js_sys::Reflect::set(&bag, &JsValue::from_str(name), &array).is_err() {
33            return false;
34        }
35        transfer.push(&array.buffer());
36    }
37    js_sys::Reflect::set(envelope, &JsValue::from_str(ATTACHMENTS_KEY), &bag).is_ok()
38}
39
40/// Decodes the control message riding `data`, if any.
41pub fn control_value_from(data: &JsValue) -> Option<JsValue> {
42    field(data, MESSAGE_KEY)
43}
44
45/// Decodes the application message riding `data`, if any.
46pub fn app_value_from(data: &JsValue) -> Option<JsValue> {
47    field(data, APP_KEY)
48}
49
50fn field(data: &JsValue, key: &str) -> Option<JsValue> {
51    let value = js_sys::Reflect::get(data, &JsValue::from_str(key)).ok()?;
52    if value.is_undefined() || value.is_null() {
53        return None;
54    }
55    Some(value)
56}
57
58/// Decodes the named binary attachments riding `data`, empty when none did.
59pub fn attachments_from(data: &JsValue) -> Attachments {
60    let mut attachments = Vec::new();
61    let Ok(value) = js_sys::Reflect::get(data, &JsValue::from_str(ATTACHMENTS_KEY)) else {
62        return attachments;
63    };
64    if !value.is_object() {
65        return attachments;
66    }
67    let object: js_sys::Object = value.clone().unchecked_into();
68    for key in js_sys::Object::keys(&object).iter() {
69        if let Some(name) = key.as_string()
70            && let Ok(entry) = js_sys::Reflect::get(&value, &key)
71            && let Ok(array) = entry.dyn_into::<js_sys::Uint8Array>()
72        {
73            attachments.push((name, array.to_vec()));
74        }
75    }
76    attachments
77}