Skip to content

Commit

Permalink
feat: Support sending/receiving messages with attachments (refs #24)
Browse files Browse the repository at this point in the history
  • Loading branch information
nesium committed Feb 9, 2024
1 parent 23a1044 commit 71ac3e7
Show file tree
Hide file tree
Showing 33 changed files with 710 additions and 80 deletions.
16 changes: 14 additions & 2 deletions bindings/prose-sdk-js/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ use crate::delegate::{Delegate, JSDelegate};
use crate::types::{
try_user_id_vec_from_string_array, AccountInfo, Availability, BareJid, Channel, ChannelsArray,
ConnectionError, Contact, ContactsArray, IntoJSArray, PresenceSubRequest,
PresenceSubRequestArray, PresenceSubRequestId, SidebarItem, SidebarItemsArray, UserBasicInfo,
UserBasicInfoArray, UserMetadata, UserProfile,
PresenceSubRequestArray, PresenceSubRequestId, SidebarItem, SidebarItemsArray, UploadSlot,
UserBasicInfo, UserBasicInfoArray, UserMetadata, UserProfile,
};

type Result<T, E = JsError> = std::result::Result<T, E>;
Expand Down Expand Up @@ -596,6 +596,18 @@ impl Client {
.map_err(WasmError::from)?;
Ok(())
}

/// Request a slot for uploading a file to attach it to a message.
#[wasm_bindgen(js_name = "requestUploadSlot")]
pub async fn request_upload_slot(&self, file_name: &str, file_size: u64) -> Result<UploadSlot> {
let slot = self
.client
.uploads
.request_upload_slot(file_name, file_size)
.await
.map_err(WasmError::from)?;
Ok(slot.into())
}
}

impl From<ProseClient> for Client {
Expand Down
6 changes: 6 additions & 0 deletions bindings/prose-sdk-js/src/types/js_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ extern "C" {

#[wasm_bindgen(typescript_type = "Contact[]")]
pub type ContactsArray;

#[wasm_bindgen(typescript_type = "Attachment[]")]
pub type AttachmentsArray;

#[wasm_bindgen(typescript_type = "UploadHeader[]")]
pub type UploadHeadersArray;
}

impl From<Vec<prose_core_client::dtos::Message>> for MessagesArray {
Expand Down
60 changes: 59 additions & 1 deletion bindings/prose-sdk-js/src/types/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use wasm_bindgen::prelude::*;

use prose_core_client::dtos;

use crate::types::{BareJid, IntoJSArray};
use crate::types::{AttachmentsArray, BareJid, IntoJSArray};

use super::{BareJidArray, ReactionsArray};

Expand All @@ -31,6 +31,46 @@ pub struct MessageMetadata {
#[wasm_bindgen]
pub struct MessageSender(dtos::MessageSender);

#[wasm_bindgen]
#[derive(Clone)]
pub struct Attachment {
#[wasm_bindgen(skip)]
pub url: String,
#[wasm_bindgen(skip)]
pub description: Option<String>,
}

#[wasm_bindgen]
impl Attachment {
#[wasm_bindgen(constructor)]
pub fn new(url: String) -> Self {
Self {
url,
description: None,
}
}

#[wasm_bindgen(getter)]
pub fn url(&self) -> String {
self.url.clone()
}

#[wasm_bindgen(setter)]
pub fn set_url(&mut self, url: String) {
self.url = url;
}

#[wasm_bindgen(getter)]
pub fn description(&self) -> Option<String> {
self.description.clone()
}

#[wasm_bindgen(setter)]
pub fn set_description(&mut self, description: Option<String>) {
self.description = description;
}
}

#[wasm_bindgen]
impl Message {
#[wasm_bindgen(getter)]
Expand Down Expand Up @@ -95,6 +135,15 @@ impl Message {
.map(|r| Reaction::from(r.clone()))
.collect_into_js_array::<ReactionsArray>()
}

#[wasm_bindgen(getter)]
pub fn attachments(&self) -> AttachmentsArray {
self.0
.attachments
.iter()
.map(|a| Attachment::from(a.clone()))
.collect_into_js_array::<AttachmentsArray>()
}
}

#[wasm_bindgen]
Expand Down Expand Up @@ -156,3 +205,12 @@ impl From<dtos::Reaction> for Reaction {
}
}
}

impl From<dtos::Attachment> for Attachment {
fn from(value: dtos::Attachment) -> Self {
Self {
url: value.url.to_string(),
description: value.description,
}
}
}
6 changes: 5 additions & 1 deletion bindings/prose-sdk-js/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ pub use connection_error::{ConnectionError, ConnectionErrorType};
pub use contact::{Availability, Contact};
pub use jid::BareJid;
pub use js_array::*;
pub use message::Message;
pub use message::{Attachment, Message};
pub use presence_sub_request::{PresenceSubRequest, PresenceSubRequestArray, PresenceSubRequestId};
pub use room::RoomEnvelopeExt;
pub use send_message_request::SendMessageRequest;
pub use sidebar_item::{SidebarItem, SidebarItemsArray};
pub use upload_slot::UploadSlot;
pub use user_info::{ParticipantInfo, ParticipantInfoArray, UserBasicInfo, UserBasicInfoArray};
pub use user_metadata::UserMetadata;
pub use user_profile::UserProfile;
Expand All @@ -26,7 +28,9 @@ mod js_array;
mod message;
mod presence_sub_request;
mod room;
mod send_message_request;
mod sidebar_item;
mod upload_slot;
mod user_info;
mod user_metadata;
mod user_profile;
10 changes: 5 additions & 5 deletions bindings/prose-sdk-js/src/types/room.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use prose_core_client::services::{
use crate::client::WasmError;
use crate::types::{
try_user_id_vec_from_string_array, MessagesArray, ParticipantInfo, ParticipantInfoArray,
StringArray, UserBasicInfo, UserBasicInfoArray,
SendMessageRequest, StringArray, UserBasicInfo, UserBasicInfoArray,
};

use super::IntoJSArray;
Expand Down Expand Up @@ -242,19 +242,19 @@ macro_rules! base_room_impl {
}

#[wasm_bindgen(js_name = "sendMessage")]
pub async fn send_message(&self, body: String) -> Result<()> {
pub async fn send_message(&self, request: SendMessageRequest) -> Result<()> {
debug!("Sending message…");
self.room
.send_message(body)
.send_message(request.try_into().map_err(WasmError::from)?)
.await
.map_err(WasmError::from)?;
Ok(())
}

#[wasm_bindgen(js_name = "updateMessage")]
pub async fn update_message(&self, message_id: &str, body: String) -> Result<()> {
pub async fn update_message(&self, message_id: &str, request: SendMessageRequest) -> Result<()> {
self.room
.update_message(message_id.into(), body)
.update_message(message_id.into(), request.try_into().map_err(WasmError::from)?)
.await
.map_err(WasmError::from)?;
Ok(())
Expand Down
127 changes: 127 additions & 0 deletions bindings/prose-sdk-js/src/types/send_message_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// prose-core-client/prose-sdk-js
//
// Copyright: 2023, Marc Bauer <mb@nesium.com>
// License: Mozilla Public License v2.0 (MPL v2.0)

use anyhow::anyhow;
use js_sys::{Object, Reflect};
use tracing::error;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};

use prose_core_client::dtos;

use crate::types::{Attachment, AttachmentsArray, IntoJSArray};

#[wasm_bindgen]
pub struct SendMessageRequest {
body: Option<String>,
attachments: Vec<Attachment>,
}

#[wasm_bindgen]
impl SendMessageRequest {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
body: None,
attachments: vec![],
}
}

#[wasm_bindgen(getter)]
pub fn body(&self) -> Option<String> {
self.body.clone()
}

#[wasm_bindgen(setter)]
pub fn set_body(&mut self, body: Option<String>) {
self.body = body;
}

#[wasm_bindgen(getter)]
pub fn attachments(&self) -> AttachmentsArray {
self.attachments.iter().cloned().collect_into_js_array()
}

#[wasm_bindgen(setter)]
pub fn set_attachments(&mut self, attachments: AttachmentsArray) {
let js_val: &JsValue = attachments.as_ref();
let array: Option<&js_sys::Array> = js_val.dyn_ref();

let Some(array) = array else {
error!("Tried to assign a non-Array to 'attachments' of 'SendMessageRequest'.");
return;
};

let Ok(length) = usize::try_from(array.length()) else {
error!("Could not determine the length of the attachments array.");
return;
};

let mut typed_array = Vec::<Attachment>::with_capacity(length);
for js in array.iter() {
let obj = match js.dyn_into::<js_sys::Object>() {
Ok(obj) => obj,
Err(err) => {
error!("Failed to parse attachment. {:?}", err);
return;
}
};

let attachment = match Attachment::try_from(obj) {
Ok(attachment) => attachment,
Err(err) => {
error!("Failed to parse attachment. {}", err.to_string());
return;
}
};
typed_array.push(attachment);
}

self.attachments = typed_array;
}
}

impl TryFrom<SendMessageRequest> for dtos::SendMessageRequest {
type Error = anyhow::Error;

fn try_from(value: SendMessageRequest) -> Result<Self, Self::Error> {
Ok(Self {
body: value.body,
attachments: value
.attachments
.into_iter()
.map(TryFrom::try_from)
.collect::<Result<Vec<_>, _>>()?,
})
}
}

impl TryFrom<Attachment> for dtos::Attachment {
type Error = anyhow::Error;

fn try_from(value: Attachment) -> Result<Self, Self::Error> {
Ok(Self {
url: value.url.parse()?,
description: value.description,
})
}
}

impl TryFrom<Object> for Attachment {
type Error = anyhow::Error;

fn try_from(value: Object) -> Result<Self, Self::Error> {
let url = Reflect::get(&value, &JsValue::from_str("url"))
.ok()
.and_then(|value| value.as_string())
.ok_or_else(|| anyhow!("url is not a String"))?;

let description = Reflect::get(&value, &JsValue::from_str("description"))
.ok()
.and_then(|value| value.as_string());

Ok(Attachment { url, description })
}
}
74 changes: 74 additions & 0 deletions bindings/prose-sdk-js/src/types/upload_slot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// prose-core-client/prose-sdk-js
//
// Copyright: 2023, Marc Bauer <mb@nesium.com>
// License: Mozilla Public License v2.0 (MPL v2.0)

use wasm_bindgen::prelude::wasm_bindgen;

use prose_core_client::dtos;

use crate::types::{IntoJSArray, UploadHeadersArray};

#[wasm_bindgen]
pub struct UploadSlot {
upload_url: String,
upload_headers: Vec<UploadHeader>,
download_url: String,
}

#[wasm_bindgen]
impl UploadSlot {
#[wasm_bindgen(getter, js_name = "uploadURL")]
pub fn upload_url(&self) -> String {
self.upload_url.clone()
}

#[wasm_bindgen(getter, js_name = "uploadHeaders")]
pub fn upload_headers(&self) -> UploadHeadersArray {
self.upload_headers.iter().cloned().collect_into_js_array()
}

#[wasm_bindgen(getter, js_name = "downloadURL")]
pub fn download_url(&self) -> String {
self.download_url.clone()
}
}

#[wasm_bindgen]
#[derive(Clone)]
pub struct UploadHeader {
name: String,
value: String,
}

#[wasm_bindgen]
impl UploadHeader {
#[wasm_bindgen(getter)]
pub fn name(&self) -> String {
self.name.clone()
}

#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
self.value.clone()
}
}

impl From<dtos::UploadHeader> for UploadHeader {
fn from(value: dtos::UploadHeader) -> Self {
Self {
name: value.name,
value: value.value,
}
}
}

impl From<dtos::UploadSlot> for UploadSlot {
fn from(value: dtos::UploadSlot) -> Self {
Self {
upload_url: value.upload_url.to_string(),
upload_headers: value.upload_headers.into_iter().map(Into::into).collect(),
download_url: value.download_url.to_string(),
}
}
}
Loading

0 comments on commit 71ac3e7

Please sign in to comment.