use base64_light::base64_decode;
use gloo_utils::format::JsValueSerdeExt;
use js_sys::Uint8Array;
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Formatter;
use std::ops::Deref;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use yrs::updates::decoder::{Decode, DecoderV1};
use yrs::updates::encoder::{Encode, Encoder};
use yrs::{Assoc, ReadTxn, StickyIndex, Transact, TransactionMut, Update};
mod array;
mod awareness;
mod collection;
mod doc;
mod js;
mod map;
mod text;
mod transaction;
mod undo;
mod weak;
mod xml_elem;
mod xml_frag;
mod xml_text;
type Result<T> = std::result::Result<T, JsValue>;
pub use crate::array::YArray as Array;
pub use crate::array::YArrayEvent as ArrayEvent;
pub use crate::doc::YDoc as Doc;
use crate::js::Shared;
pub use crate::map::YMap as Map;
pub use crate::map::YMapEvent as MapEvent;
pub use crate::text::YText as Text;
pub use crate::text::YTextEvent as TextEvent;
pub use crate::transaction::ImplicitTransaction;
pub use crate::transaction::YTransaction as Transaction;
use crate::transaction::YTransaction;
pub use crate::undo::YUndoEvent as UndoEvent;
pub use crate::undo::YUndoManager as UndoManager;
pub use crate::weak::YWeakLink as WeakLink;
pub use crate::weak::YWeakLinkEvent as WeakLinkEvent;
#[wasm_bindgen(js_name = setPanicHook)]
pub fn set_panic_hook() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
#[wasm_bindgen(js_name = encodeStateVector)]
pub fn encode_state_vector(doc: &Doc) -> Result<js_sys::Uint8Array> {
let txn = doc
.0
.try_transact()
.map_err(|_| JsValue::from_str(crate::js::errors::ANOTHER_RW_TX))?;
let bytes = txn.state_vector().encode_v1();
Ok(js_sys::Uint8Array::from(bytes.as_slice()))
}
#[wasm_bindgen(js_name = debugUpdateV1)]
pub fn debug_update_v1(update: js_sys::Uint8Array) -> Result<String> {
let update: Vec<u8> = update.to_vec();
let mut decoder = DecoderV1::from(update.as_slice());
match Update::decode(&mut decoder) {
Ok(update) => Ok(format!("{:#?}", update)),
Err(e) => Err(JsValue::from(e.to_string())),
}
}
#[wasm_bindgen(js_name = debugUpdateV2)]
pub fn debug_update_v2(update: js_sys::Uint8Array) -> Result<String> {
let mut update: Vec<u8> = update.to_vec();
match Update::decode_v2(update.as_mut_slice()) {
Ok(update) => Ok(format!("{:#?}", update)),
Err(e) => Err(JsValue::from(e.to_string())),
}
}
#[wasm_bindgen(js_name = encodeStateAsUpdate)]
pub fn encode_state_as_update(
doc: &Doc,
vector: Option<js_sys::Uint8Array>,
) -> Result<js_sys::Uint8Array> {
let txn = doc
.0
.try_transact()
.map_err(|_| JsValue::from_str(crate::js::errors::ANOTHER_RW_TX))?;
let sv = crate::js::convert::state_vector_from_js(vector)?.unwrap_or_default();
let bytes = txn.encode_state_as_update_v1(&sv);
Ok(bytes.as_slice().into())
}
#[wasm_bindgen(js_name = encodeStateAsUpdateV2)]
pub fn encode_state_as_update_v2(
doc: &Doc,
vector: Option<js_sys::Uint8Array>,
) -> Result<js_sys::Uint8Array> {
let txn = doc
.0
.try_transact()
.map_err(|_| JsValue::from_str(crate::js::errors::ANOTHER_RW_TX))?;
let sv = crate::js::convert::state_vector_from_js(vector)?.unwrap_or_default();
let bytes = txn.encode_state_as_update_v2(&sv);
Ok(bytes.as_slice().into())
}
#[wasm_bindgen(js_name = applyUpdate)]
pub fn apply_update(doc: &Doc, update: js_sys::Uint8Array, origin: JsValue) -> Result<()> {
let txn = if !origin.is_undefined() {
doc.0.try_transact_mut_with(js::Js::from(origin))
} else {
doc.0.try_transact_mut()
};
let mut txn = txn.map_err(|_| JsValue::from_str(crate::js::errors::ANOTHER_TX))?;
let diff: Vec<u8> = update.to_vec();
match Update::decode_v1(&diff) {
Ok(update) => Ok(txn.apply_update(update)),
Err(e) => Err(JsValue::from(e.to_string())),
}
}
#[wasm_bindgen(js_name = applyUpdateV2)]
pub fn apply_update_v2(doc: &Doc, update: js_sys::Uint8Array, origin: JsValue) -> Result<()> {
let txn = if !origin.is_undefined() {
doc.0.try_transact_mut_with(js::Js::from(origin))
} else {
doc.0.try_transact_mut()
};
let mut txn = txn.map_err(|_| JsValue::from_str(crate::js::errors::ANOTHER_TX))?;
let diff: Vec<u8> = update.to_vec();
match Update::decode_v2(&diff) {
Ok(update) => Ok(txn.apply_update(update)),
Err(e) => Err(JsValue::from(e.to_string())),
}
}
struct Snapshot(yrs::Snapshot);
impl Deref for Snapshot {
type Target = yrs::Snapshot;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'de> Deserialize<'de> for Snapshot {
#[inline]
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct SnapshotVisitor;
impl<'de> Visitor<'de> for SnapshotVisitor {
type Value = Snapshot;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
write!(formatter, "a valid snapshot")
}
#[inline]
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
where
E: Error,
{
let result = base64_light::base64_decode(v);
let snap =
yrs::Snapshot::decode_v1(&result).map_err(|e| E::custom(e.to_string()))?;
Ok(Snapshot(snap))
}
}
deserializer.deserialize_str(SnapshotVisitor)
}
}
impl Serialize for Snapshot {
#[inline]
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = self.0.encode_v1();
serializer.serialize_str(&base64_light::base64_encode_bytes(&bytes))
}
}
#[wasm_bindgen(js_name = snapshot)]
pub fn snapshot(doc: &Doc) -> crate::Result<JsValue> {
let snapshot = doc.0.transact().snapshot();
JsValue::from_serde(&Snapshot(snapshot)).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = equalSnapshots)]
pub fn equal_snapshots(snap1: &JsValue, snap2: &JsValue) -> bool {
let s1: Snapshot = snap1.into_serde().unwrap();
let s2: Snapshot = snap2.into_serde().unwrap();
s1.0 == s2.0
}
#[wasm_bindgen(js_name = encodeSnapshotV1)]
pub fn encode_snapshot_v1(snapshot: &JsValue) -> Vec<u8> {
base64_decode(&snapshot.as_string().unwrap())
}
#[wasm_bindgen(js_name = encodeSnapshotV2)]
pub fn encode_snapshot_v2(snapshot: &JsValue) -> Vec<u8> {
let s: Snapshot = snapshot.into_serde().unwrap();
s.0.encode_v2()
}
#[wasm_bindgen(js_name = decodeSnapshotV2)]
pub fn decode_snapshot_v2(snapshot: &[u8]) -> Result<JsValue> {
let s = yrs::Snapshot::decode_v2(snapshot)
.map_err(|_| JsValue::from("failed to deserialize snapshot using lib0 v2 decoding"))?;
JsValue::from_serde(&Snapshot(s)).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = decodeSnapshotV1)]
pub fn decode_snapshot_v1(snapshot: &[u8]) -> Result<JsValue> {
Ok(JsValue::from_str(&base64_light::base64_encode_bytes(
snapshot,
)))
}
#[wasm_bindgen(js_name = encodeStateFromSnapshotV1)]
pub fn encode_state_from_snapshot_v1(doc: &Doc, snapshot: JsValue) -> Result<Vec<u8>> {
let snapshot: Snapshot = snapshot
.into_serde()
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let mut encoder = yrs::updates::encoder::EncoderV1::new();
match doc
.0
.transact()
.encode_state_from_snapshot(&*snapshot, &mut encoder)
{
Ok(_) => Ok(encoder.to_vec()),
Err(e) => Err(JsValue::from(e.to_string())),
}
}
#[wasm_bindgen(js_name = encodeStateFromSnapshotV2)]
pub fn encode_state_from_snapshot_v2(doc: &Doc, snapshot: JsValue) -> Result<Vec<u8>> {
let snapshot: Snapshot = snapshot
.into_serde()
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let mut encoder = yrs::updates::encoder::EncoderV2::new();
match doc
.0
.transact()
.encode_state_from_snapshot(&*snapshot, &mut encoder)
{
Ok(_) => Ok(encoder.to_vec()),
Err(e) => Err(JsValue::from(e.to_string())),
}
}
#[wasm_bindgen(js_name=createStickyIndexFromType)]
pub fn create_sticky_index_from_type(
ytype: &JsValue,
index: u32,
assoc: i32,
txn: &ImplicitTransaction,
) -> Result<JsValue> {
if let Ok(shared) = Shared::from_ref(ytype) {
let assoc = if assoc >= 0 {
Assoc::After
} else {
Assoc::Before
};
let (branch_id, doc) = shared.try_integrated()?;
let index = match YTransaction::from_implicit(txn)? {
Some(txn) => {
let txn: &TransactionMut = (&*txn).deref();
let ptr = match branch_id.get_branch(txn) {
None => return Err(JsValue::from_str(crate::js::errors::REF_DISPOSED)),
Some(ptr) => ptr,
};
StickyIndex::at(&*txn, ptr, index, assoc)
}
None => {
let txn = doc
.try_transact()
.map_err(|_| JsValue::from_str(crate::js::errors::ANOTHER_RW_TX))?;
let ptr = match branch_id.get_branch(&txn) {
None => return Err(JsValue::from_str(crate::js::errors::REF_DISPOSED)),
Some(ptr) => ptr,
};
StickyIndex::at(&txn, ptr, index, assoc)
}
};
JsValue::from_serde(&index).map_err(|e| JsValue::from_str(&e.to_string()))
} else {
Err(JsValue::from_str(crate::js::errors::NOT_WASM_OBJ))
}
}
#[wasm_bindgen(js_name=createOffsetFromStickyIndex)]
pub fn create_offset_from_sticky_index(rpos: &JsValue, doc: &Doc) -> Result<JsValue> {
let pos: StickyIndex =
JsValue::into_serde(rpos).map_err(|e| JsValue::from_str(&e.to_string()))?;
let txn = doc.0.transact();
if let Some(abs) = pos.get_offset(&txn) {
#[derive(Serialize)]
struct AbsolutePos {
index: u32,
assoc: Assoc,
}
let abs = AbsolutePos {
index: abs.index,
assoc: abs.assoc,
};
JsValue::from_serde(&abs).map_err(|e| JsValue::from_str(&e.to_string()))
} else {
Ok(JsValue::NULL)
}
}
#[wasm_bindgen(js_name=encodeStickyIndex)]
pub fn encode_sticky_index(rpos: &JsValue) -> Result<Uint8Array> {
let pos: StickyIndex =
JsValue::into_serde(rpos).map_err(|e| JsValue::from_str(&e.to_string()))?;
let bytes = Uint8Array::from(pos.encode_v1().as_slice());
Ok(bytes)
}
#[wasm_bindgen(js_name=decodeStickyIndex)]
pub fn decode_sticky_index(bin: Uint8Array) -> Result<JsValue> {
let data: Vec<u8> = bin.to_vec();
match StickyIndex::decode_v1(&data) {
Ok(index) => JsValue::from_serde(&index).map_err(|e| JsValue::from_str(&e.to_string())),
Err(err) => Err(JsValue::from_str(&err.to_string())),
}
}