Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32164480
D15371.1765048283.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
D15371.1765048283.diff
View Options
diff --git a/services/tunnelbroker/src/notifs/generic_client.rs b/services/tunnelbroker/src/notifs/generic_client.rs
new file mode 100644
--- /dev/null
+++ b/services/tunnelbroker/src/notifs/generic_client.rs
@@ -0,0 +1,343 @@
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+
+use crate::amqp_client::utils::{BasicMessageSender, SendMessageError};
+use crate::constants::error_types;
+use crate::database::DatabaseClient;
+use crate::notifs::Notif;
+
+use super::apns::APNsNotif;
+use super::base::BaseNotifClient;
+use super::fcm::firebase_message::FCMMessage;
+use super::web_push::WebPushNotif;
+use super::wns::WNSNotif;
+use super::NotifType;
+
+#[derive(
+ Serialize, Deserialize, PartialEq, Debug, Clone, derive_more::Display,
+)]
+#[serde(rename_all = "lowercase")]
+pub enum NotifPlatform {
+ #[display = "android"]
+ Android,
+ #[display = "ios"]
+ Ios,
+ #[display = "web"]
+ Web,
+ #[display = "windows"]
+ Windows,
+ #[display = "macos"]
+ MacOS,
+}
+
+impl From<grpc_clients::identity::protos::unauth::DeviceType>
+ for NotifPlatform
+{
+ fn from(value: grpc_clients::identity::protos::unauth::DeviceType) -> Self {
+ use grpc_clients::identity::protos::unauth::DeviceType;
+ match value {
+ DeviceType::Web => Self::Web,
+ DeviceType::Ios => Self::Ios,
+ DeviceType::Android => Self::Android,
+ DeviceType::Windows => Self::Windows,
+ DeviceType::MacOs => Self::MacOS,
+ DeviceType::Keyserver => unreachable!(),
+ }
+ }
+}
+
+impl NotifType {
+ fn for_platform(platform: &NotifPlatform) -> Self {
+ match platform {
+ NotifPlatform::Ios | NotifPlatform::MacOS => Self::APNs,
+ NotifPlatform::Android => Self::FCM,
+ NotifPlatform::Web => Self::WebPush,
+ NotifPlatform::Windows => Self::WNS,
+ }
+ }
+}
+
+pub struct NotifRecipientDescriptor {
+ pub device_id: String,
+ pub platform: NotifPlatform,
+}
+
+#[derive(Clone, Debug, Serialize)]
+pub struct GenericNotifPayload {
+ pub title: String,
+ pub body: String,
+ pub thread_id: String,
+}
+
+enum APNsTopic {
+ Ios,
+ MacOS,
+}
+
+impl APNsTopic {
+ fn as_str(&self) -> &str {
+ match self {
+ APNsTopic::Ios => "app.comm",
+ APNsTopic::MacOS => "app.comm.macos",
+ }
+ }
+}
+
+#[derive(
+ Debug, derive_more::Display, derive_more::Error, derive_more::From,
+)]
+pub enum DeviceTokenError {
+ DatabaseError(Box<comm_lib::database::Error>),
+ MissingDeviceToken,
+ InvalidDeviceToken,
+ InvalidNotifProvider,
+}
+
+#[derive(
+ Debug, derive_more::Display, derive_more::Error, derive_more::From,
+)]
+pub enum GenericNotifClientError {
+ Provider(super::base::NotifClientError),
+ TokenError(DeviceTokenError),
+ CommunicationError(SendMessageError),
+ SerializationError(serde_json::Error),
+}
+
+impl GenericNotifClientError {
+ /// True if error is caused by missing or expired device token.
+ pub fn is_invalid_token(&self) -> bool {
+ use DeviceTokenError::{InvalidDeviceToken, MissingDeviceToken};
+ match self {
+ Self::Provider(e) if e.should_invalidate_token() => true,
+ Self::TokenError(err) => {
+ matches!(err, MissingDeviceToken | InvalidDeviceToken)
+ }
+ _ => false,
+ }
+ }
+}
+
+impl GenericNotifPayload {
+ fn into_apns(self, device_token: &str, topic: APNsTopic) -> APNsNotif {
+ use super::apns::headers::NotificationHeaders;
+ use super::apns::headers::PushType;
+
+ let headers = NotificationHeaders {
+ apns_topic: Some(topic.as_str().into()),
+ apns_push_type: Some(PushType::Alert),
+ apns_id: Some(uuid::Uuid::new_v4().to_string()),
+ apns_expiration: None,
+ apns_priority: None,
+ apns_collapse_id: None,
+ };
+
+ let payload = json!({
+ "aps": {
+ "alert": {
+ "title": self.title,
+ "body": self.body,
+ },
+ "thread-id": self.thread_id,
+ "sound": "default",
+ "mutable-content": 1
+ },
+ });
+
+ APNsNotif {
+ device_token: device_token.to_string(),
+ headers,
+ payload: serde_json::to_string(&payload).unwrap(),
+ }
+ }
+
+ fn into_fcm(self, device_token: &str) -> FCMMessage {
+ use super::fcm::firebase_message::{AndroidConfig, AndroidMessagePriority};
+
+ let data = json!({
+ "id": uuid::Uuid::new_v4().to_string(),
+ "title": self.title,
+ "body": self.body,
+ "threadID": self.thread_id,
+ "badgeOnly": "0",
+ });
+
+ FCMMessage {
+ data,
+ token: device_token.to_string(),
+ android: AndroidConfig {
+ priority: AndroidMessagePriority::Normal,
+ },
+ }
+ }
+
+ fn into_web_push(self, device_token: &str) -> WebPushNotif {
+ use crate::notifs::web_push::WebPushNotif;
+
+ let payload = json!({
+ "id": uuid::Uuid::new_v4().to_string(),
+ "title": self.title,
+ "body": self.body,
+ "threadID": self.thread_id,
+ });
+
+ WebPushNotif {
+ device_token: device_token.to_string(),
+ payload: serde_json::to_string(&payload).unwrap(),
+ }
+ }
+
+ fn into_wns(self, device_token: &str) -> WNSNotif {
+ let payload = json!({
+ "title": self.title,
+ "body": self.body,
+ "threadID": self.thread_id,
+ });
+
+ WNSNotif {
+ device_token: device_token.to_string(),
+ payload: serde_json::to_string(&payload).unwrap(),
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct GenericNotifClient {
+ inner: BaseNotifClient,
+ db_client: DatabaseClient,
+ message_sender: BasicMessageSender,
+}
+
+impl GenericNotifClient {
+ pub fn new(
+ db_client: DatabaseClient,
+ message_sender: BasicMessageSender,
+ ) -> Self {
+ Self {
+ inner: BaseNotifClient::new(),
+ db_client,
+ message_sender,
+ }
+ }
+
+ async fn get_device_token(
+ &self,
+ device_id: String,
+ notif_type: NotifType,
+ ) -> Result<String, DeviceTokenError> {
+ use crate::database::DeviceTokenEntry;
+
+ let db_token = self
+ .db_client
+ .get_device_token(&device_id)
+ .await
+ .map_err(|err| DeviceTokenError::DatabaseError(Box::new(err)))?;
+
+ match db_token {
+ None => Err(DeviceTokenError::MissingDeviceToken),
+ Some(DeviceTokenEntry {
+ device_token,
+ token_invalid,
+ platform,
+ }) => {
+ if token_invalid {
+ return Err(DeviceTokenError::InvalidDeviceToken);
+ }
+
+ if platform.is_some_and(|p| !notif_type.supported_platform(p)) {
+ return Err(DeviceTokenError::InvalidNotifProvider);
+ }
+
+ Ok(device_token)
+ }
+ }
+ }
+
+ async fn invalidate_device_token(
+ &self,
+ device_id: String,
+ invalidated_token: String,
+ ) -> Result<(), GenericNotifClientError> {
+ use tunnelbroker_messages::bad_device_token::BadDeviceToken;
+ use tunnelbroker_messages::MessageToDeviceRequest;
+
+ tracing::debug!(
+ "Invalidating notif device token for device: {}",
+ device_id
+ );
+
+ let message = BadDeviceToken { invalidated_token };
+ let message_request = MessageToDeviceRequest {
+ device_id: device_id.to_string(),
+ payload: serde_json::to_string(&message)?,
+ client_message_id: uuid::Uuid::new_v4().to_string(),
+ };
+
+ self
+ .message_sender
+ .send_message_to_device(&message_request)
+ .await?;
+
+ self
+ .db_client
+ .mark_device_token_as_invalid(&device_id)
+ .await
+ .map_err(|err| DeviceTokenError::DatabaseError(err.into()))?;
+
+ Ok(())
+ }
+
+ pub async fn send_notif(
+ &self,
+ notif: GenericNotifPayload,
+ target: NotifRecipientDescriptor,
+ ) -> Result<(), GenericNotifClientError> {
+ let NotifRecipientDescriptor {
+ device_id,
+ platform,
+ } = target;
+ tracing::trace!(device_id, "Sending notif for platform: {platform}");
+
+ let device_token = self
+ .get_device_token(device_id.clone(), NotifType::for_platform(&platform))
+ .await?;
+
+ let target_notif: Notif = match &platform {
+ NotifPlatform::Android => notif.into_fcm(&device_token).into(),
+ NotifPlatform::Ios => {
+ notif.into_apns(&device_token, APNsTopic::Ios).into()
+ }
+ NotifPlatform::MacOS => {
+ notif.into_apns(&device_token, APNsTopic::MacOS).into()
+ }
+ NotifPlatform::Web => notif.into_web_push(&device_token).into(),
+ NotifPlatform::Windows => notif.into_wns(&device_token).into(),
+ };
+
+ let token_error = match self.inner.send_notif(target_notif).await {
+ Ok(()) => return Ok(()),
+ Err(e) if !e.should_invalidate_token() => {
+ tracing::error!(
+ "Error when sending notif for platform {}: {:?}",
+ platform,
+ e
+ );
+ return Err(e.into());
+ }
+ Err(token_error) => token_error,
+ };
+
+ if let Err(e) = self
+ .invalidate_device_token(device_id, device_token.clone())
+ .await
+ {
+ tracing::error!(
+ errorType = error_types::DDB_ERROR,
+ "Error invalidating device token {}: {:?}",
+ device_token,
+ e
+ );
+ };
+
+ Err(token_error.into())
+ }
+}
diff --git a/services/tunnelbroker/src/notifs/mod.rs b/services/tunnelbroker/src/notifs/mod.rs
--- a/services/tunnelbroker/src/notifs/mod.rs
+++ b/services/tunnelbroker/src/notifs/mod.rs
@@ -4,9 +4,14 @@
pub mod wns;
mod base;
+mod generic_client;
mod session_client;
pub use base::{Notif, NotifClientError};
+pub use generic_client::{
+ GenericNotifClient, GenericNotifClientError, GenericNotifPayload,
+ NotifRecipientDescriptor,
+};
pub use session_client::SessionNotifClient;
#[derive(Debug, derive_more::Display, PartialEq)]
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 6, 7:11 PM (17 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5840065
Default Alt Text
D15371.1765048283.diff (9 KB)
Attached To
Mode
D15371: [tunnelbroker] Implement server-native notif client
Attached
Detach File
Event Timeline
Log In to Comment