nightshade-api 0.53.0

Procedural high level API for the nightshade game engine
Documentation
//! Envelope encoding shared by the page and the worker: every
//! `postMessage` is a plain object whose fields are the
//! [`crate::wire::MESSAGE_KEY`] control message or the
//! [`crate::wire::APP_KEY`] application message, plus optional
//! [`crate::wire::ATTACHMENTS_KEY`] binary payloads whose buffers ride the
//! transfer list.

use crate::wire::{APP_KEY, ATTACHMENTS_KEY, Attachments, MESSAGE_KEY};
use serde::Serialize;
use wasm_bindgen::{JsCast, JsValue};

/// Builds an envelope carrying `message` under `key`, or `None` when the
/// message does not serialize.
pub fn envelope_with<Message: Serialize>(key: &str, message: &Message) -> Option<js_sys::Object> {
    let value = serde_wasm_bindgen::to_value(message).ok()?;
    let envelope = js_sys::Object::new();
    js_sys::Reflect::set(&envelope, &JsValue::from_str(key), &value).ok()?;
    Some(envelope)
}

/// Adds named binary payloads to `envelope` and their buffers to
/// `transfer`. Returns false when the attachment bag could not be written.
pub fn attach(
    envelope: &js_sys::Object,
    transfer: &js_sys::Array,
    attachments: &[(String, Vec<u8>)],
) -> bool {
    let bag = js_sys::Object::new();
    for (name, bytes) in attachments {
        let array = js_sys::Uint8Array::new_with_length(bytes.len() as u32);
        array.copy_from(bytes);
        if js_sys::Reflect::set(&bag, &JsValue::from_str(name), &array).is_err() {
            return false;
        }
        transfer.push(&array.buffer());
    }
    js_sys::Reflect::set(envelope, &JsValue::from_str(ATTACHMENTS_KEY), &bag).is_ok()
}

/// Decodes the control message riding `data`, if any.
pub fn control_value_from(data: &JsValue) -> Option<JsValue> {
    field(data, MESSAGE_KEY)
}

/// Decodes the application message riding `data`, if any.
pub fn app_value_from(data: &JsValue) -> Option<JsValue> {
    field(data, APP_KEY)
}

fn field(data: &JsValue, key: &str) -> Option<JsValue> {
    let value = js_sys::Reflect::get(data, &JsValue::from_str(key)).ok()?;
    if value.is_undefined() || value.is_null() {
        return None;
    }
    Some(value)
}

/// Decodes the named binary attachments riding `data`, empty when none did.
pub fn attachments_from(data: &JsValue) -> Attachments {
    let mut attachments = Vec::new();
    let Ok(value) = js_sys::Reflect::get(data, &JsValue::from_str(ATTACHMENTS_KEY)) else {
        return attachments;
    };
    if !value.is_object() {
        return attachments;
    }
    let object: js_sys::Object = value.clone().unchecked_into();
    for key in js_sys::Object::keys(&object).iter() {
        if let Some(name) = key.as_string()
            && let Ok(entry) = js_sys::Reflect::get(&value, &key)
            && let Ok(array) = entry.dyn_into::<js_sys::Uint8Array>()
        {
            attachments.push((name, array.to_vec()));
        }
    }
    attachments
}