feat: javascript glue

Co-authored-by: alemi.dev <me@alemi.dev>
This commit is contained in:
frelodev 2024-08-08 19:54:47 +02:00
parent f699d2e8fe
commit 754b88fd73
6 changed files with 47 additions and 104 deletions

View file

@ -52,6 +52,7 @@ impl AuthWrap {
/// will disconnect when dropped /// will disconnect when dropped
/// can be used to interact with server /// can be used to interact with server
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "js", napi_derive::napi)]
pub struct Client(Arc<ClientInner>); pub struct Client(Arc<ClientInner>);
#[derive(Debug)] #[derive(Debug)]

View file

@ -1,27 +1,11 @@
use std::sync::Arc;
use napi::threadsafe_function::{ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode}; use napi::threadsafe_function::{ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode};
use napi_derive::napi; use napi_derive::napi;
use crate::api::TextChange; use crate::api::TextChange;
use crate::ffi::js::JsCodempError;
use crate::api::Controller; use crate::api::Controller;
use crate::prelude::*; use crate::prelude::*;
impl From<crate::Error> for napi::Error {
fn from(value: crate::Error) -> Self {
let msg = format!("{value}");
match value {
crate::Error::Deadlocked => napi::Error::new(napi::Status::WouldDeadlock, msg),
_ => napi::Error::new(napi::Status::GenericFailure, msg),
}
}
}
#[napi] #[napi]
impl CodempBufferController { impl CodempBufferController {
#[napi(ts_args_type = "fun: (event: JsTextChange) => void")] #[napi(ts_args_type = "fun: (event: JsTextChange) => void")]
pub fn callback(&self, fun: napi::JsFunction) -> napi::Result<()>{ pub fn callback(&self, fun: napi::JsFunction) -> napi::Result<()>{
let tsfn : ThreadsafeFunction<crate::api::TextChange, Fatal> = let tsfn : ThreadsafeFunction<crate::api::TextChange, Fatal> =
@ -47,14 +31,13 @@ impl CodempBufferController {
Ok(()) Ok(())
} }
#[napi(js_name = "recv")] #[napi(js_name = "recv")]
pub async fn jsrecv(&self) -> napi::Result<TextChange> { pub async fn js_recv(&self) -> napi::Result<TextChange> {
Ok(self.recv().await?.into()) Ok(self.recv().await?.into())
} }
#[napi] #[napi(js_name = "send")]
pub fn send(&self, op: TextChange) -> napi::Result<()> { pub fn js_send(&self, op: TextChange) -> napi::Result<()> {
Ok(self.send(op)?) Ok(self.send(op)?)
} }
} }

View file

@ -1,43 +1,37 @@
use napi_derive::napi; use napi_derive::napi;
use crate::ffi::js::JsCodempError; use crate::prelude::*;
#[napi]
/// main codemp client session
pub struct JsCodempClient(tokio::sync::RwLock<crate::Client>);
#[napi] #[napi]
/// connect to codemp servers and return a client session /// connect to codemp servers and return a client session
pub async fn connect(addr: Option<String>) -> napi::Result<JsCodempClient>{ pub async fn connect(addr: Option<String>, username: String, password: String) -> napi::Result<CodempClient>{
let client = crate::Client::new(addr.as_deref().unwrap_or("http://codemp.alemi.dev:50053")) let client = crate::Client::new(addr.as_deref().unwrap_or("http://codemp.alemi.dev:50053"), username, password)
.await?; .await?;
Ok(JsCodempClient(tokio::sync::RwLock::new(client))) Ok(client)
}
#[napi]
impl JsCodempClient {
#[napi]
/// login against AuthService with provided credentials, optionally requesting access to a workspace
pub async fn login(&self, username: String, password: String, workspace_id: Option<String>) -> napi::Result<()> {
self.0.read().await.login(username, password, workspace_id).await?;
Ok(())
} }
#[napi] #[napi]
impl CodempClient {
#[napi(js_name = "join_workspace")]
/// join workspace with given id (will start its cursor controller) /// join workspace with given id (will start its cursor controller)
pub async fn join_workspace(&self, workspace: String) -> napi::Result<JsWorkspace> { pub async fn js_join_workspace(&self, workspace: String) -> napi::Result<CodempWorkspace> {
Ok(JsWorkspace::from(self.0.write().await.join_workspace(&workspace).await?)) Ok(self.join_workspace(workspace).await?)
} }
#[napi] #[napi(js_name = "get_workspace")]
/// get workspace with given id, if it exists /// get workspace with given id, if it exists
pub async fn get_workspace(&self, workspace: String) -> Option<JsWorkspace> { pub fn js_get_workspace(&self, workspace: String) -> Option<CodempWorkspace> {
self.0.read().await.get_workspace(&workspace).map(|w| JsWorkspace::from(w)) self.get_workspace(&workspace)
} }
#[napi] #[napi(js_name = "user_id")]
/// return current sessions's user id /// return current sessions's user id
pub async fn user_id(&self) -> String { pub fn js_user_id(&self) -> String {
self.0.read().await.user_id().to_string() self.user_id().to_string()
}
#[napi(js_name = "active_workspaces")]
pub fn js_active_workspaces(&self) -> Vec<String> {
self.active_workspaces()
} }
} }

View file

@ -1,14 +1,10 @@
use std::sync::Arc;
use napi_derive::napi; use napi_derive::napi;
use uuid::Uuid;
use napi::threadsafe_function::{ThreadsafeFunction, ThreadSafeCallContext, ThreadsafeFunctionCallMode, ErrorStrategy}; use napi::threadsafe_function::{ThreadsafeFunction, ThreadSafeCallContext, ThreadsafeFunctionCallMode, ErrorStrategy};
use crate::api::Controller; use crate::api::Controller;
use crate::prelude::*; use crate::prelude::*;
#[napi(object, js_name = "Cursor")]
#[napi(js_name = "Cursor")]
pub struct JsCursor { pub struct JsCursor {
/// range of text change, as char indexes in buffer previous state /// range of text change, as char indexes in buffer previous state
pub start_row: i32, pub start_row: i32,
@ -46,7 +42,6 @@ impl From<CodempCursor> for JsCursor {
#[napi] #[napi]
impl CodempCursorController { impl CodempCursorController {
#[napi(ts_args_type = "fun: (event: JsCursorEvent) => void")] #[napi(ts_args_type = "fun: (event: JsCursorEvent) => void")]
pub fn callback(&self, fun: napi::JsFunction) -> napi::Result<()>{ pub fn callback(&self, fun: napi::JsFunction) -> napi::Result<()>{
let tsfn : ThreadsafeFunction<JsCursor, ErrorStrategy::Fatal> = let tsfn : ThreadsafeFunction<JsCursor, ErrorStrategy::Fatal> =
@ -70,46 +65,14 @@ impl CodempCursorController {
Ok(()) Ok(())
} }
#[napi] #[napi(js_name = "send")]
pub fn send(&self, pos: &CodempCursorController) -> napi::Result<()> { pub fn js_send(&self, pos: JsCursor) -> napi::Result<()> {
Ok(self.send(pos)?) Ok(self.send(crate::api::Cursor::from(pos))?)
}
} }
#[napi(js_name= "try_recv")]
#[derive(Debug)] pub fn js_try_recv(&self) -> napi::Result<Option<JsCursor>> {
#[napi(object)] Ok(self.try_recv()?.map(|x| JsCursor::from(x)))
pub struct JsCursorEvent {
pub user: String,
pub buffer: String,
pub start: JsRowCol,
pub end: JsRowCol,
}
impl From::<codemp_proto::cursor::CursorEvent> for JsCursorEvent {
fn from(value: codemp_proto::cursor::CursorEvent) -> Self {
let pos = value.position;
let start = pos.start;
let end = pos.end;
JsCursorEvent {
user: Uuid::from(value.user).to_string(),
buffer: pos.buffer.into(),
start: JsRowCol { row: start.row, col: start.col },
end: JsRowCol { row: end.row, col: end.col },
}
}
}
#[derive(Debug)]
#[napi(object)]
pub struct JsRowCol {
pub row: i32,
pub col: i32
}
impl From::<codemp_proto::cursor::RowCol> for JsRowCol {
fn from(value: codemp_proto::cursor::RowCol) -> Self {
JsRowCol { row: value.row, col: value.col }
} }
} }

View file

@ -6,12 +6,13 @@ pub mod cursor;
pub mod buffer; pub mod buffer;
pub mod op_cache; pub mod op_cache;
#[derive(Debug)] impl From<crate::Error> for napi::Error {
struct JsCodempError(crate::Error); fn from(value: crate::Error) -> Self {
let msg = format!("{value}");
impl From::<JsCodempError> for napi::Error { match value {
fn from(value: JsCodempError) -> Self { crate::Error::Deadlocked => napi::Error::new(napi::Status::WouldDeadlock, msg),
napi::Error::new(napi::Status::GenericFailure, &format!("CodempError: {:?}", value)) _ => napi::Error::new(napi::Status::GenericFailure, msg),
}
} }
} }
@ -36,11 +37,12 @@ impl JsLogger {
.with_line_number(false) .with_line_number(false)
.with_source_location(false) .with_source_location(false)
.compact(); .compact();
tracing_subscriber::fmt() let _initialized = tracing_subscriber::fmt()
.event_format(format) .event_format(format)
.with_max_level(level) .with_max_level(level)
.with_writer(std::sync::Mutex::new(JsLoggerProducer(tx))) .with_writer(std::sync::Mutex::new(JsLoggerProducer(tx)))
.init(); .try_init()
.is_ok();
JsLogger(std::sync::Arc::new(tokio::sync::Mutex::new(rx))) JsLogger(std::sync::Arc::new(tokio::sync::Mutex::new(rx)))
} }

View file

@ -36,9 +36,9 @@ impl CodempWorkspace {
Ok(self.attach(&path).await?) Ok(self.attach(&path).await?)
} }
/*#[napi] #[napi(js_name = "delete")]
pub async fn delete(&self, path: String) -> napi::Result<>{ pub async fn js_delete(&self, path: String) -> napi::Result<()> {
self.0.delete(&path) Ok(self.delete(&path).await?)
}*/ }
} }