diff --git a/config.yaml b/config.yaml index 4a23bda..0e9c90b 100644 --- a/config.yaml +++ b/config.yaml @@ -35,7 +35,7 @@ independent-push-service: control-token: BADTOKENTOCHANGE # Public access URL pattern (for clients access) {TOKEN_URL} will be replaced by client token - public-url: ws://localhost:4500/ws/{TOKEN_URL} + public-url: ws://192.168.1.9:4500/ws/{TOKEN_URL} # Database configuration @@ -57,4 +57,4 @@ rtc-relay: - stun:stun.l.google.com:19302 max-users-per-calls: 10 allow-video: true - max-users-per-video-calls: 6 \ No newline at end of file + max-users-per-video-calls: 6 diff --git a/src/data/mod.rs b/src/data/mod.rs index 40cc9b9..219b9db 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -38,4 +38,5 @@ pub mod new_custom_emoji; pub mod user_ws_message; pub mod user_ws_connection; pub mod call_signal; -pub mod new_notifications_settings; \ No newline at end of file +pub mod new_notifications_settings; +pub mod push_notification; \ No newline at end of file diff --git a/src/data/push_notification.rs b/src/data/push_notification.rs new file mode 100644 index 0000000..d68928b --- /dev/null +++ b/src/data/push_notification.rs @@ -0,0 +1,14 @@ +//! # Push notification +//! +//! Notification pushed to registered devices +//! +//! @author Pierre Hubert + +#[derive(Debug)] +pub struct PushNotification { + pub id: String, + pub title: String, + pub body: String, + pub image: Option, + pub timeout: Option, +} \ No newline at end of file diff --git a/src/data/user.rs b/src/data/user.rs index 352e7c5..a3dab88 100644 --- a/src/data/user.rs +++ b/src/data/user.rs @@ -36,6 +36,11 @@ impl UserID { false => None, } } + + /// Create an owned instance of this user ID object + pub fn as_owned(&self) -> Self { + Self(self.0) + } } impl Hash for UserID { @@ -136,6 +141,10 @@ pub struct User { } impl User { + pub fn full_name(&self) -> String { + format!("{} {}", self.first_name, self.last_name) + } + /// Check if user's page is public pub fn is_page_public(&self) -> bool { !matches!(self.status, UserPageStatus::PRIVATE) diff --git a/src/helpers/conversations_helper.rs b/src/helpers/conversations_helper.rs index 6efe89b..8de301d 100644 --- a/src/helpers/conversations_helper.rs +++ b/src/helpers/conversations_helper.rs @@ -461,6 +461,9 @@ pub fn mark_user_seen(conv_id: ConvID, user_id: &UserID, last_msg: &Conversation .set_u64("last_access", last_msg.time_sent) .exec()?; + // Push an event + events_helper::propagate_event(&Event::SeenLastConversationMessage(user_id, conv_id))?; + // Push an event (updated_number_unread_conversations) events_helper::propagate_event(&Event::UpdatedNumberUnreadConversations(&vec![user_id.clone()]))?; diff --git a/src/helpers/events_helper.rs b/src/helpers/events_helper.rs index 4ecc21a..dabfc53 100644 --- a/src/helpers/events_helper.rs +++ b/src/helpers/events_helper.rs @@ -13,6 +13,7 @@ use crate::data::error::Res; use crate::data::user::UserID; use crate::data::user_token::UserAccessToken; use crate::data::user_ws_connection::UserWsConnection; +use crate::helpers::push_notifications_helper; pub enum Event<'a> { /// Websocket of a user was closed @@ -26,6 +27,9 @@ pub enum Event<'a> { /// Updated the number of notifications of one of multiple users user UpdatedNotificationsNumber(&'a Vec), + /// Indicate that a user has seen the last message of a conversation + SeenLastConversationMessage(&'a UserID, ConvID), + /// Updated the number of unread conversations UpdatedNumberUnreadConversations(&'a Vec), @@ -89,5 +93,6 @@ pub fn propagate_event(e: &Event) -> Res { user_ws_controller::handle_event(e)?; calls_controller::handle_event(e)?; rtc_relay_controller::handle_event(e)?; + push_notifications_helper::handle_event(e)?; Ok(()) } \ No newline at end of file diff --git a/src/helpers/push_notifications_helper.rs b/src/helpers/push_notifications_helper.rs index c9e4e57..bcd906e 100644 --- a/src/helpers/push_notifications_helper.rs +++ b/src/helpers/push_notifications_helper.rs @@ -2,9 +2,15 @@ //! //! @author Pierre Hubert -use crate::data::user_token::{UserAccessToken, PushNotificationToken}; +use crate::controllers::user_ws_controller; +use crate::data::conversation::ConvID; +use crate::data::conversation_message::ConversationMessage; use crate::data::error::Res; -use crate::helpers::independent_push_notifications_service_helper; +use crate::data::push_notification::PushNotification; +use crate::data::user::UserID; +use crate::data::user_token::{PushNotificationToken, UserAccessToken}; +use crate::helpers::{account_helper, conversations_helper, independent_push_notifications_service_helper, user_helper}; +use crate::helpers::events_helper::Event; /// Un-register for previous push notifications service pub fn un_register_from_previous_service(client: &UserAccessToken) -> Res { @@ -16,5 +22,128 @@ pub fn un_register_from_previous_service(client: &UserAccessToken) -> Res { } } + Ok(()) +} + +/// Push a notification to specific tokens +fn push_notification(n: &PushNotification, targets: Vec) -> Res { + // TODO : implement + println!("* Push notification: {:#?} \n* To {:?}", n, targets); + + Ok(()) +} + +/// Cancel a notification for specific tokens (optional) +fn cancel_notification(id: String, targets: Vec) -> Res { + // TODO : implement + println!("* Cancel push notification: {:#?} \n* For {:?}", id, targets); + + Ok(()) +} + +/// Push a notification to specific users, only if they are not connected +fn push_notification_to_users(n: &PushNotification, users: Vec) -> Res { + let dest_users: Vec = users.into_iter() + .filter(|u| !user_ws_controller::is_user_connected(u)) + .collect(); + + let mut dest_tokens = vec![]; + + for user in dest_users { + for token in account_helper::get_all_login_tokens(&user)? { + dest_tokens.push(token.push_notifications_token); + } + } + + push_notification(n, dest_tokens) +} + +/// Cancel a notification for one or more users +fn cancel_notification_for_users(id: String, users: Vec) -> Res { + let mut dest_tokens = vec![]; + + for user in users { + for token in account_helper::get_all_login_tokens(&user)? { + dest_tokens.push(token.push_notifications_token); + } + } + + cancel_notification(id, dest_tokens) +} + +/// Create a conversation notification +pub fn create_conversation_notification(msg: &ConversationMessage) -> Res { + let user_id = match &msg.user_id { + Some(id) => id, + None => { + // Do no create notifications for server messages + return Ok(()); + } + }; + + let user = user_helper::find_user_by_id(user_id)?; + let conv = conversations_helper::get_single(msg.conv_id)?; + + let notif_title = match conv.name { + None => user.full_name(), + Some(name) => format!("{} ({})", user.full_name(), name) + }; + + let notif_message = match &msg.message { + None => "Shared file".to_string(), + Some(msg) => msg.to_string() + }; + + let notif = PushNotification { + id: format!("conv-{}", msg.conv_id.id()), + title: notif_title, + body: notif_message, + image: None, + timeout: None, + }; + + let list = conversations_helper::get_list_members(msg.conv_id)? + .into_iter() + .filter(|m| m.following && m.user_id != user_id) + .map(|m| m.user_id) + .collect(); + + push_notification_to_users(¬if, list) +} + +/// Dismiss a conversation notification +pub fn cancel_conversation_notification(conv_id: ConvID, users: Option>) -> Res { + let notif_id = format!("conv-{}", conv_id.id()); + + let list = match users { + Some(users) => users, + None => conversations_helper::get_list_members(conv_id)? + .into_iter().map(|m| m.user_id).collect() + }; + + cancel_notification_for_users(notif_id, list) +} + + +/// Handle event. This method NEVER returns Err +pub fn handle_event(e: &Event) -> Res { + if let Err(e) = handle_event_internal(e) { + eprintln!("Failed to create push notifications associated with event! {}", e); + } + + Ok(()) +} + +fn handle_event_internal(e: &Event) -> Res { + match e { + Event::NewConversationMessage(msg) => { + create_conversation_notification(msg)?; + } + Event::SeenLastConversationMessage(user_id, conv_id) => { + cancel_conversation_notification(*conv_id, Some(vec![user_id.as_owned()]))?; + } + _ => {} + } + Ok(()) } \ No newline at end of file